c9f33abf7abc45ff063defac0f2cad4fbaa0fab6
[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 #include <errno.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27
28 #define DEBUG 0
29
30 #if DEBUG
31 #define TELCMDS
32 #define TELOPTS
33 #endif
34
35 #include <arpa/telnet.h>
36
37 #define SOCKET_FILE "/run/vpp/cli.sock"
38
39 volatile int window_resized = 0;
40 struct termios orig_tio;
41
42 static void
43 send_ttype (int sock_fd, int is_interactive)
44 {
45   char *term;
46   static char buf[2048];
47
48   /* wipe the buffer so there is no potential
49    * for inter-invocation leakage */
50   memset (buf, 0, sizeof (buf));
51
52   term = is_interactive ? getenv ("TERM") : "vppctl";
53   if (term == NULL)
54     term = "dumb";
55
56   int len = snprintf (buf, sizeof (buf),
57                       "%c%c%c"
58                       "%c%s"
59                       "%c%c",
60                       IAC, SB, TELOPT_TTYPE, 0, term, IAC, SE);
61   if (send (sock_fd, buf, len, 0) < 0)
62     {
63       perror ("send_ttype");
64     }
65 }
66
67 static void
68 send_naws (int sock_fd)
69 {
70   struct winsize ws;
71   static char buf[2048];
72
73   memset (buf, 0, sizeof (buf));
74   if (ioctl (STDIN_FILENO, TIOCGWINSZ, &ws) < 0)
75     {
76       fprintf (stderr, "ioctl(TIOCGWINSZ)");
77       return;
78     }
79
80   int len = snprintf (buf, sizeof (buf),
81                       "%c%c%c"
82                       "%c%c%c%c"
83                       "%c%c",
84                       IAC, SB, TELOPT_NAWS, ws.ws_col >> 8, ws.ws_col & 0xff,
85                       ws.ws_row >> 8, ws.ws_row & 0xff, IAC, SE);
86   int n_written = write (sock_fd, buf, len);
87   if (n_written < len)
88     {
89       perror ("send_naws");
90     }
91 }
92
93 static void
94 signal_handler_winch (int signum)
95 {
96   window_resized = 1;
97 }
98
99 static void
100 signal_handler_term (int signum)
101 {
102   tcsetattr (STDIN_FILENO, TCSAFLUSH, &orig_tio);
103 }
104
105 static int
106 process_input (int sock_fd, unsigned char *rx_buf, int rx_buf_len,
107                int is_interactive, int *sent_ttype)
108 {
109   int i = 0;
110   int j = 0;
111
112   while (i < rx_buf_len)
113     {
114       if (rx_buf[i] == IAC)
115         {
116           if (rx_buf[i + 1] == SB)
117             {
118               char opt = rx_buf[i + 2];
119               i += 3;
120 #if DEBUG
121               if (rx_buf[i] != IAC)
122                 {
123                   fprintf (stderr, "SB ");
124                 }
125               while (rx_buf[i] != IAC && i < rx_buf_len)
126                 fprintf (stderr, "%02x ", rx_buf[i++]);
127               fprintf (stderr, "\n");
128 #else
129               while (rx_buf[i] != IAC && i < rx_buf_len)
130                 {
131                   i++;
132                 }
133 #endif
134               i += 2;
135               if (opt == TELOPT_TTYPE)
136                 {
137                   send_ttype (sock_fd, is_interactive);
138                   *sent_ttype = 1;
139                 }
140               else if (is_interactive && opt == TELOPT_NAWS)
141                 send_naws (sock_fd);
142             }
143           else
144             {
145 #if DEBUG
146               fprintf (stderr, "IAC at %d, IAC %s %s", i,
147                        TELCMD (rx_buf[i + 1]), TELOPT (rx_buf[i + 2]));
148 #endif
149               i += 3;
150             }
151         }
152       else
153         {
154           /* i is always the same or ahead of j, so at worst this is a no-op */
155           rx_buf[j] = rx_buf[i];
156           i++;
157           j++;
158         }
159     }
160   return j;
161 }
162
163
164 int
165 main (int argc, char *argv[])
166 {
167   struct epoll_event event;
168   struct sigaction sa;
169   struct termios tio;
170   int efd = -1;
171   char *cmd = 0;
172   int cmd_len = 0;
173   int do_quit = 0;
174   int is_interactive = 0;
175   int acked = 1;                /* counts messages from VPP; starts at 1 */
176   int sent_ttype = 0;
177   char *sock_fname = SOCKET_FILE;
178   int sock_fd = -1;
179   int error = 0;
180   int arg = 0;
181
182   /* process command line */
183   argc--;
184   argv++;
185
186   if (argc > 1 && strncmp (argv[0], "-s", 2) == 0)
187     {
188       sock_fname = argv[1];
189       argc -= 2;
190       argv += 2;
191     }
192
193   struct sockaddr_un saddr = { 0 };
194   saddr.sun_family = AF_UNIX;
195   strncpy (saddr.sun_path, sock_fname, sizeof (saddr.sun_path) - 1);
196
197   sock_fd = socket (AF_UNIX, SOCK_STREAM, 0);
198   if (sock_fd < 0)
199     {
200       perror ("socket");
201       exit (1);
202     }
203
204   if (connect (sock_fd, (struct sockaddr *) &saddr, sizeof (saddr)) < 0)
205     {
206       perror ("connect");
207       exit (1);
208     }
209
210   for (arg = 0; arg < argc; arg++)
211     {
212       cmd_len += strlen (argv[arg]) + 1;
213     }
214   if (cmd_len > 0)
215     {
216       cmd_len++; // account for \n in the end
217       cmd = malloc (cmd_len);
218       if (!cmd)
219         {
220           error = errno;
221           perror ("malloc failed");
222           goto done;
223         }
224       memset (cmd, 0, cmd_len);
225       while (argc--)
226         {
227           strncat (cmd, *argv++, cmd_len);
228           strncat (cmd, " ", cmd_len);
229         }
230       cmd[cmd_len - 2] = '\n';
231       cmd[cmd_len - 1] = 0;
232     }
233
234   is_interactive = isatty (STDIN_FILENO) && cmd == 0;
235
236   if (is_interactive)
237     {
238       /* Capture terminal resize events */
239       memset (&sa, 0, sizeof (struct sigaction));
240       sa.sa_handler = signal_handler_winch;
241       if (sigaction (SIGWINCH, &sa, 0) < 0)
242         {
243           error = errno;
244           perror ("sigaction for SIGWINCH");
245           goto done;
246         }
247
248       /* Capture SIGTERM to reset tty settings */
249       sa.sa_handler = signal_handler_term;
250       if (sigaction (SIGTERM, &sa, 0) < 0)
251         {
252           error = errno;
253           perror ("sigaction for SIGTERM");
254           goto done;
255         }
256
257       /* Save the original tty state so we can restore it later */
258       if (tcgetattr (STDIN_FILENO, &orig_tio) < 0)
259         {
260           error = errno;
261           perror ("tcgetattr");
262           goto done;
263         }
264
265       /* Tweak the tty settings */
266       tio = orig_tio;
267       /* echo off, canonical mode off, ext'd input processing off */
268       tio.c_lflag &= ~(ECHO | ICANON | IEXTEN);
269       tio.c_cc[VMIN] = 1;       /* 1 byte at a time */
270       tio.c_cc[VTIME] = 0;      /* no timer */
271
272       if (tcsetattr (STDIN_FILENO, TCSAFLUSH, &tio) < 0)
273         {
274           error = errno;
275           perror ("tcsetattr");
276           goto done;
277         }
278     }
279
280   efd = epoll_create1 (0);
281
282   /* register STDIN */
283   event.events = EPOLLIN | EPOLLPRI | EPOLLERR;
284   event.data.fd = STDIN_FILENO;
285   if (epoll_ctl (efd, EPOLL_CTL_ADD, STDIN_FILENO, &event) != 0)
286     {
287       /* ignore EPERM; it means stdin is something like /dev/null */
288       if (errno != EPERM)
289         {
290           error = errno;
291           fprintf (stderr, "epoll_ctl[%d]", STDIN_FILENO);
292           perror (0);
293           goto done;
294         }
295     }
296
297   /* register socket */
298   event.events = EPOLLIN | EPOLLPRI | EPOLLERR;
299   event.data.fd = sock_fd;
300   if (epoll_ctl (efd, EPOLL_CTL_ADD, sock_fd, &event) != 0)
301     {
302       error = errno;
303       fprintf (stderr, "epoll_ctl[%d]", sock_fd);
304       perror (0);
305       goto done;
306     }
307
308   while (1)
309     {
310       int n;
311       static int sent_cmd = 0;
312
313       if (window_resized)
314         {
315           window_resized = 0;
316           send_naws (sock_fd);
317         }
318
319       if ((n = epoll_wait (efd, &event, 1, -1)) < 0)
320         {
321           /* maybe we received signal */
322           if (errno == EINTR)
323             continue;
324
325           error = errno;
326           perror ("epoll_wait");
327           goto done;
328         }
329
330       if (n == 0)
331         continue;
332
333       if (event.data.fd == STDIN_FILENO)
334         {
335           int n;
336           char c[100];
337
338           if (!sent_ttype)
339             continue;           /* not ready for this yet */
340
341           n = read (STDIN_FILENO, c, sizeof (c));
342           if (n > 0)
343             {
344               int n_written = write (sock_fd, c, n);
345               if (n_written < n)
346                 error = errno;
347               if (error)
348                 goto done;
349             }
350           else if (n < 0)
351             fprintf (stderr, "read rv=%d", n);
352           else /* EOF */
353             do_quit = 1;
354         }
355       else if (event.data.fd == sock_fd)
356         {
357           unsigned char rx_buf[100];
358           memset (rx_buf, 0, sizeof (rx_buf));
359           int nread = recv (sock_fd, rx_buf, sizeof (rx_buf), 0);
360
361           if (nread < 0)
362             error = errno;
363           if (error)
364             break;
365
366           if (nread == 0)
367             break;
368
369           int len = process_input (sock_fd, rx_buf, nread, is_interactive,
370                                    &sent_ttype);
371
372           if (len > 0)
373             {
374               unsigned char *p = rx_buf, *q = rx_buf;
375
376               while (len)
377                 {
378                   /* Search for and skip NUL bytes */
379                   while (q < (p + len) && *q)
380                     q++;
381
382                   n = write (STDOUT_FILENO, p, q - p);
383                   if (n < 0)
384                     {
385                       error = errno;
386                       perror ("write");
387                       goto done;
388                     }
389
390                   while (q < (p + len) && !*q)
391                     {
392                       q++;
393                       acked++;  /* every NUL is an acknowledgement */
394                     }
395                   len -= q - p;
396                   p = q;
397                 }
398             }
399
400           if (do_quit && do_quit < acked)
401             {
402               /* Ask the other end to close the connection */
403               char quit_str[] = "quit\n";
404               int n = write (sock_fd, quit_str, strlen (quit_str));
405               if (n < strlen (quit_str))
406                 {
407                   error = errno;
408                   perror ("write quit");
409                 }
410               do_quit = 0;
411             }
412           if (cmd && sent_ttype && !sent_cmd)
413             {
414               /* We wait until after the TELNET TTYPE option has been sent.
415                * That is to make sure the session at the VPP end has switched
416                * to line-by-line mode, and thus avoid prompts and echoing.
417                * Note that it does also disable further TELNET option processing.
418                */
419               int n_written = write (sock_fd, cmd, strlen (cmd) + 1);
420               sent_cmd = 1;
421               if (n_written < strlen (cmd))
422                 {
423                   error = errno;
424                   perror ("write command");
425                   goto done;
426                 }
427               do_quit = acked;  /* quit after the next response */
428             }
429         }
430       else
431         {
432           error = errno;
433           perror ("unknown fd");
434           goto done;
435         }
436     }
437
438   close (sock_fd);
439
440 done:
441   free (cmd);
442   if (efd > -1)
443     close (efd);
444
445   if (is_interactive)
446     tcsetattr (STDIN_FILENO, TCSAFLUSH, &orig_tio);
447
448   if (error)
449     {
450       return 1;
451     }
452
453   return 0;
454 }
455
456 /* *INDENT-ON* */
457
458 /*
459  * fd.io coding-style-patch-verification: ON
460  *
461  * Local Variables:
462  * eval: (c-set-style "gnu")
463  * End:
464  */