f81a0ce985ad5938cd975a0a2a398e1941248585
[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_interactive)
45 {
46   char *term;
47
48   term = is_interactive ? getenv ("TERM") : "vppctl";
49   if (term == NULL)
50     term = "dumb";
51
52   clib_socket_tx_add_formatted (s, "%c%c%c" "%c%s" "%c%c",
53                                 IAC, SB, TELOPT_TTYPE, 0, term, IAC, SE);
54   clib_socket_tx (s);
55 }
56
57 static void
58 send_naws (clib_socket_t * s)
59 {
60   struct winsize ws;
61
62   if (ioctl (STDIN_FILENO, TIOCGWINSZ, &ws) < 0)
63     {
64       clib_unix_warning ("ioctl(TIOCGWINSZ)");
65       return;
66     }
67
68   clib_socket_tx_add_formatted (s, "%c%c%c" "%c%c%c%c" "%c%c",
69                                 IAC, SB, TELOPT_NAWS,
70                                 ws.ws_col >> 8, ws.ws_col & 0xff,
71                                 ws.ws_row >> 8, ws.ws_row & 0xff, IAC, SE);
72   clib_socket_tx (s);
73 }
74
75 static void
76 signal_handler_winch (int signum)
77 {
78   window_resized = 1;
79 }
80
81 static void
82 signal_handler_term (int signum)
83 {
84   tcsetattr (STDIN_FILENO, TCSAFLUSH, &orig_tio);
85 }
86
87 static u8 *
88 process_input (u8 * str, clib_socket_t * s, int is_interactive,
89                int *sent_ttype)
90 {
91   int i = 0;
92
93   while (i < vec_len (s->rx_buffer))
94     {
95       if (s->rx_buffer[i] == IAC)
96         {
97           if (s->rx_buffer[i + 1] == SB)
98             {
99               u8 *sb = 0;
100               char opt = s->rx_buffer[i + 2];
101               i += 3;
102               while (s->rx_buffer[i] != IAC)
103                 vec_add1 (sb, s->rx_buffer[i++]);
104
105 #if DEBUG
106               clib_warning ("SB %s\n  %U", TELOPT (opt),
107                             format_hexdump, sb, vec_len (sb));
108 #endif
109               vec_free (sb);
110               i += 2;
111               if (opt == TELOPT_TTYPE)
112                 {
113                   send_ttype (s, is_interactive);
114                   *sent_ttype = 1;
115                 }
116               else if (is_interactive && opt == TELOPT_NAWS)
117                 send_naws (s);
118             }
119           else
120             {
121 #if DEBUG
122               clib_warning ("IAC at %d, IAC %s %s", i,
123                             TELCMD (s->rx_buffer[i + 1]),
124                             TELOPT (s->rx_buffer[i + 2]));
125 #endif
126               i += 3;
127             }
128         }
129       else
130         vec_add1 (str, s->rx_buffer[i++]);
131     }
132   vec_reset_length (s->rx_buffer);
133   return str;
134 }
135
136
137 int
138 main (int argc, char *argv[])
139 {
140   clib_socket_t _s = { 0 }, *s = &_s;
141   clib_error_t *error = 0;
142   struct epoll_event event;
143   struct sigaction sa;
144   struct termios tio;
145   int efd = -1;
146   u8 *str = 0;
147   u8 *cmd = 0;
148   int do_quit = 0;
149   int is_interactive = 0;
150   int sent_ttype = 0;
151
152
153   clib_mem_init (0, 64ULL << 10);
154
155   /* process command line */
156   argc--;
157   argv++;
158
159   if (argc > 1 && strcmp (argv[0], "-s") == 0)
160     {
161       s->config = argv[1];
162       argc -= 2;
163       argv += 2;
164     }
165   else
166     s->config = SOCKET_FILE;
167
168   while (argc--)
169     cmd = format (cmd, "%s%c", (argv++)[0], argc ? ' ' : 0);
170
171   s->flags = CLIB_SOCKET_F_IS_CLIENT;
172
173   error = clib_socket_init (s);
174   if (error)
175     goto done;
176
177   is_interactive = isatty (STDIN_FILENO) && cmd == 0;
178
179   if (is_interactive)
180     {
181       /* Capture terminal resize events */
182       memset (&sa, 0, sizeof (struct sigaction));
183       sa.sa_handler = signal_handler_winch;
184       if (sigaction (SIGWINCH, &sa, 0) < 0)
185         {
186           error = clib_error_return_unix (0, "sigaction");
187           goto done;
188         }
189
190       /* Capture SIGTERM to reset tty settings */
191       sa.sa_handler = signal_handler_term;
192       if (sigaction (SIGTERM, &sa, 0) < 0)
193         {
194           error = clib_error_return_unix (0, "sigaction");
195           goto done;
196         }
197
198       /* Save the original tty state so we can restore it later */
199       if (tcgetattr (STDIN_FILENO, &orig_tio) < 0)
200         {
201           error = clib_error_return_unix (0, "tcgetattr");
202           goto done;
203         }
204
205       /* Tweak the tty settings */
206       tio = orig_tio;
207       /* echo off, canonical mode off, ext'd input processing off */
208       tio.c_lflag &= ~(ECHO | ICANON | IEXTEN);
209       tio.c_cc[VMIN] = 1;       /* 1 byte at a time */
210       tio.c_cc[VTIME] = 0;      /* no timer */
211
212       if (tcsetattr (STDIN_FILENO, TCSAFLUSH, &tio) < 0)
213         {
214           error = clib_error_return_unix (0, "tcsetattr");
215           goto done;
216         }
217     }
218
219   efd = epoll_create1 (0);
220
221   /* register STDIN */
222   event.events = EPOLLIN | EPOLLPRI | EPOLLERR;
223   event.data.fd = STDIN_FILENO;
224   if (epoll_ctl (efd, EPOLL_CTL_ADD, STDIN_FILENO, &event) != 0)
225     {
226       /* ignore EPERM; it means stdin is something like /dev/null */
227       if (errno != EPERM)
228         {
229           error = clib_error_return_unix (0, "epoll_ctl[%d]", STDIN_FILENO);
230           goto done;
231         }
232     }
233
234   /* register socket */
235   event.events = EPOLLIN | EPOLLPRI | EPOLLERR;
236   event.data.fd = s->fd;
237   if (epoll_ctl (efd, EPOLL_CTL_ADD, s->fd, &event) != 0)
238     {
239       error = clib_error_return_unix (0, "epoll_ctl[%d]", s->fd);
240       goto done;
241     }
242
243   while (1)
244     {
245       int n;
246
247       if (window_resized)
248         {
249           window_resized = 0;
250           send_naws (s);
251         }
252
253       if ((n = epoll_wait (efd, &event, 1, -1)) < 0)
254         {
255           /* maybe we received signal */
256           if (errno == EINTR)
257             continue;
258
259           error = clib_error_return_unix (0, "epoll_wait");
260           goto done;
261         }
262
263       if (n == 0)
264         continue;
265
266       if (event.data.fd == STDIN_FILENO)
267         {
268           int n;
269           char c[100];
270
271           if (!sent_ttype)
272             continue;           /* not ready for this yet */
273
274           n = read (STDIN_FILENO, c, sizeof (c));
275           if (n > 0)
276             {
277               memcpy (clib_socket_tx_add (s, n), c, n);
278               error = clib_socket_tx (s);
279               if (error)
280                 goto done;
281             }
282           else if (n < 0)
283             clib_warning ("read rv=%d", n);
284           else                  /* EOF */
285             do_quit = 1;
286         }
287       else if (event.data.fd == s->fd)
288         {
289           error = clib_socket_rx (s, 100);
290           if (error)
291             break;
292
293           if (clib_socket_rx_end_of_file (s))
294             break;
295
296           str = process_input (str, s, is_interactive, &sent_ttype);
297
298           if (vec_len (str) > 0)
299             {
300               int len = vec_len (str);
301               u8 *p = str, *q = str;
302
303               while (len)
304                 {
305                   /* Search for and skip NUL bytes */
306                   while (q < (p + len) && *q)
307                     q++;
308
309                   n = write (STDOUT_FILENO, p, q - p);
310                   if (n < 0)
311                     {
312                       error = clib_error_return_unix (0, "write");
313                       goto done;
314                     }
315
316                   while (q < (p + len) && !*q)
317                     q++;
318                   len -= q - p;
319                   p = q;
320                 }
321
322               vec_reset_length (str);
323             }
324
325           if (do_quit)
326             {
327               /* Ask the other end to close the connection */
328               clib_socket_tx_add_formatted (s, "quit\n");
329               clib_socket_tx (s);
330               do_quit = 0;
331             }
332           if (cmd && sent_ttype)
333             {
334               /* We wait until after the TELNET TTYPE option has been sent.
335                * That is to make sure the session at the VPP end has switched
336                * to line-by-line mode, and thus avoid prompts and echoing.
337                * Note that it does also disable further TELNET option processing.
338                */
339               clib_socket_tx_add_formatted (s, "%s\n", cmd);
340               clib_socket_tx (s);
341               vec_free (cmd);
342               do_quit = 1;
343             }
344         }
345       else
346         {
347           error = clib_error_return (0, "unknown fd");
348           goto done;
349         }
350     }
351
352   error = clib_socket_close (s);
353
354 done:
355   vec_free (cmd);
356   vec_free (str);
357   if (efd > -1)
358     close (efd);
359
360   if (is_interactive)
361     tcsetattr (STDIN_FILENO, TCSAFLUSH, &orig_tio);
362
363   if (error)
364     {
365       clib_error_report (error);
366       return 1;
367     }
368
369   return 0;
370 }
371
372 /* *INDENT-ON* */
373
374 /*
375  * fd.io coding-style-patch-verification: ON
376  *
377  * Local Variables:
378  * eval: (c-set-style "gnu")
379  * End:
380  */