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