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