misc: vppctl - fix coverity warning
[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   unsigned long 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
196   if (strlen (sock_fname) > sizeof (saddr.sun_path) - 1)
197     {
198       perror ("socket path too long");
199       exit (1);
200     }
201
202   strncpy (saddr.sun_path, sock_fname, sizeof (saddr.sun_path) - 1);
203
204   sock_fd = socket (AF_UNIX, SOCK_STREAM, 0);
205   if (sock_fd < 0)
206     {
207       perror ("socket");
208       exit (1);
209     }
210
211   if (connect (sock_fd, (struct sockaddr *) &saddr, sizeof (saddr)) < 0)
212     {
213       perror ("connect");
214       exit (1);
215     }
216
217   for (arg = 0; arg < argc; arg++)
218     {
219       cmd_len += strlen (argv[arg]) + 1;
220     }
221   if (cmd_len > 0)
222     {
223       cmd_len++; // account for 0 at end
224       cmd = malloc (cmd_len);
225       if (!cmd)
226         {
227           error = errno;
228           perror ("malloc failed");
229           goto done;
230         }
231       memset (cmd, 0, cmd_len);
232       unsigned long space_left = cmd_len - 1; // reserve space for 0 at end
233       while (argc--)
234         {
235           strncat (cmd, *argv, space_left);
236           space_left -= strlen (*argv);
237           ++argv;
238           strncat (cmd, " ", space_left);
239           --space_left;
240         }
241       cmd[cmd_len - 2] = '\n';
242       cmd[cmd_len - 1] = 0;
243     }
244
245   is_interactive = isatty (STDIN_FILENO) && cmd == 0;
246
247   if (is_interactive)
248     {
249       /* Capture terminal resize events */
250       memset (&sa, 0, sizeof (struct sigaction));
251       sa.sa_handler = signal_handler_winch;
252       if (sigaction (SIGWINCH, &sa, 0) < 0)
253         {
254           error = errno;
255           perror ("sigaction for SIGWINCH");
256           goto done;
257         }
258
259       /* Capture SIGTERM to reset tty settings */
260       sa.sa_handler = signal_handler_term;
261       if (sigaction (SIGTERM, &sa, 0) < 0)
262         {
263           error = errno;
264           perror ("sigaction for SIGTERM");
265           goto done;
266         }
267
268       /* Save the original tty state so we can restore it later */
269       if (tcgetattr (STDIN_FILENO, &orig_tio) < 0)
270         {
271           error = errno;
272           perror ("tcgetattr");
273           goto done;
274         }
275
276       /* Tweak the tty settings */
277       tio = orig_tio;
278       /* echo off, canonical mode off, ext'd input processing off */
279       tio.c_lflag &= ~(ECHO | ICANON | IEXTEN);
280       tio.c_cc[VMIN] = 1;       /* 1 byte at a time */
281       tio.c_cc[VTIME] = 0;      /* no timer */
282
283       if (tcsetattr (STDIN_FILENO, TCSAFLUSH, &tio) < 0)
284         {
285           error = errno;
286           perror ("tcsetattr");
287           goto done;
288         }
289     }
290
291   efd = epoll_create1 (0);
292
293   /* register STDIN */
294   event.events = EPOLLIN | EPOLLPRI | EPOLLERR;
295   event.data.fd = STDIN_FILENO;
296   if (epoll_ctl (efd, EPOLL_CTL_ADD, STDIN_FILENO, &event) != 0)
297     {
298       /* ignore EPERM; it means stdin is something like /dev/null */
299       if (errno != EPERM)
300         {
301           error = errno;
302           fprintf (stderr, "epoll_ctl[%d]", STDIN_FILENO);
303           perror (0);
304           goto done;
305         }
306     }
307
308   /* register socket */
309   event.events = EPOLLIN | EPOLLPRI | EPOLLERR;
310   event.data.fd = sock_fd;
311   if (epoll_ctl (efd, EPOLL_CTL_ADD, sock_fd, &event) != 0)
312     {
313       error = errno;
314       fprintf (stderr, "epoll_ctl[%d]", sock_fd);
315       perror (0);
316       goto done;
317     }
318
319   while (1)
320     {
321       int n;
322       static int sent_cmd = 0;
323
324       if (window_resized)
325         {
326           window_resized = 0;
327           send_naws (sock_fd);
328         }
329
330       if ((n = epoll_wait (efd, &event, 1, -1)) < 0)
331         {
332           /* maybe we received signal */
333           if (errno == EINTR)
334             continue;
335
336           error = errno;
337           perror ("epoll_wait");
338           goto done;
339         }
340
341       if (n == 0)
342         continue;
343
344       if (event.data.fd == STDIN_FILENO)
345         {
346           int n;
347           char c[100];
348
349           if (!sent_ttype)
350             continue;           /* not ready for this yet */
351
352           n = read (STDIN_FILENO, c, sizeof (c));
353           if (n > 0)
354             {
355               int n_written = write (sock_fd, c, n);
356               if (n_written < n)
357                 error = errno;
358               if (error)
359                 goto done;
360             }
361           else if (n < 0)
362             fprintf (stderr, "read rv=%d", n);
363           else /* EOF */
364             do_quit = 1;
365         }
366       else if (event.data.fd == sock_fd)
367         {
368           unsigned char rx_buf[100];
369           memset (rx_buf, 0, sizeof (rx_buf));
370           int nread = recv (sock_fd, rx_buf, sizeof (rx_buf), 0);
371
372           if (nread < 0)
373             error = errno;
374           if (error)
375             break;
376
377           if (nread == 0)
378             break;
379
380           int len = process_input (sock_fd, rx_buf, nread, is_interactive,
381                                    &sent_ttype);
382
383           if (len > 0)
384             {
385               unsigned char *p = rx_buf, *q = rx_buf;
386
387               while (len)
388                 {
389                   /* Search for and skip NUL bytes */
390                   while (q < (p + len) && *q)
391                     q++;
392
393                   n = write (STDOUT_FILENO, p, q - p);
394                   if (n < 0)
395                     {
396                       error = errno;
397                       perror ("write");
398                       goto done;
399                     }
400
401                   while (q < (p + len) && !*q)
402                     {
403                       q++;
404                       acked++;  /* every NUL is an acknowledgement */
405                     }
406                   len -= q - p;
407                   p = q;
408                 }
409             }
410
411           if (do_quit && do_quit < acked)
412             {
413               /* Ask the other end to close the connection */
414               char quit_str[] = "quit\n";
415               int n = write (sock_fd, quit_str, strlen (quit_str));
416               if (n < strlen (quit_str))
417                 {
418                   error = errno;
419                   perror ("write quit");
420                 }
421               do_quit = 0;
422             }
423           if (cmd && sent_ttype && !sent_cmd)
424             {
425               /* We wait until after the TELNET TTYPE option has been sent.
426                * That is to make sure the session at the VPP end has switched
427                * to line-by-line mode, and thus avoid prompts and echoing.
428                * Note that it does also disable further TELNET option processing.
429                */
430               int n_written = write (sock_fd, cmd, strlen (cmd) + 1);
431               sent_cmd = 1;
432               if (n_written < strlen (cmd))
433                 {
434                   error = errno;
435                   perror ("write command");
436                   goto done;
437                 }
438               do_quit = acked;  /* quit after the next response */
439             }
440         }
441       else
442         {
443           error = errno;
444           perror ("unknown fd");
445           goto done;
446         }
447     }
448
449   close (sock_fd);
450
451 done:
452   free (cmd);
453   if (efd > -1)
454     close (efd);
455
456   if (is_interactive)
457     tcsetattr (STDIN_FILENO, TCSAFLUSH, &orig_tio);
458
459   if (error)
460     {
461       return 1;
462     }
463
464   return 0;
465 }
466
467 /* *INDENT-ON* */
468
469 /*
470  * fd.io coding-style-patch-verification: ON
471  *
472  * Local Variables:
473  * eval: (c-set-style "gnu")
474  * End:
475  */