1 /*
2 * %CopyrightBegin%
3 *
4 * Copyright Ericsson AB 1996-2020. All Rights Reserved.
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 *
18 * %CopyrightEnd%
19 */
20 /*
21 * Module: to_erl.c
22 *
23 * This module implements a process that opens two specified FIFOs, one
24 * for reading and one for writing; reads from its stdin, and writes what
25 * it has read to the write FIF0; reads from the read FIFO, and writes to
26 * its stdout.
27 *
28 ________ _________
29 | |--<-- pipe.r (fifo1) --<--| |
30 | to_erl | | run_erl | (parent)
31 |________|-->-- pipe.w (fifo2) -->--|_________|
32 ^ master pty
33 |
34 | slave pty
35 ____V____
36 | |
37 | "erl" | (child)
38 |_________|
39 */
40 #ifdef HAVE_CONFIG_H
41 # include "config.h"
42 #endif
43
44 #include <sys/types.h>
45 #include <sys/stat.h>
46 #include <sys/time.h>
47 #include <sys/types.h>
48 #include <fcntl.h>
49 #include <unistd.h>
50 #include <stdio.h>
51 #include <stdlib.h>
52 #include <string.h>
53 #include <termios.h>
54 #include <dirent.h>
55 #include <signal.h>
56 #include <errno.h>
57 #ifdef HAVE_SYS_IOCTL_H
58 # include <sys/ioctl.h>
59 #endif
60
61 #include "run_erl.h"
62 #include "safe_string.h" /* strn_cpy, strn_catf, sn_printf, etc. */
63
64 #if defined(O_NONBLOCK)
65 # define DONT_BLOCK_PLEASE O_NONBLOCK
66 #else
67 # define DONT_BLOCK_PLEASE O_NDELAY
68 # if !defined(EAGAIN)
69 # define EAGAIN -3898734
70 # endif
71 #endif
72
73 #ifdef HAVE_STRERROR
74 # define STRERROR(x) strerror(x)
75 #else
76 # define STRERROR(x) ""
77 #endif
78
79 #define noDEBUG
80
81 #define PIPE_DIR "/tmp/"
82 #define PIPE_STUBNAME "erlang.pipe"
83 #define PIPE_STUBLEN strlen(PIPE_STUBNAME)
84
85 #ifdef DEBUG
86 #define STATUS(s) { fprintf(stderr, (s)); fflush(stderr); }
87 #else
88 #define STATUS(s)
89 #endif
90
91 #ifndef FILENAME_MAX
92 #define FILENAME_MAX 250
93 #endif
94
95 static struct termios tty_smode, tty_rmode;
96 static int tty_eof = 0;
97 static int recv_sig = 0;
98 static int protocol_ver = RUN_ERL_LO_VER; /* assume lowest to begin with */
99
100 static int write_all(int fd, const char* buf, int len);
101 static int window_size_seq(char* buf, size_t bufsz);
102 static int version_handshake(char* buf, int len, int wfd);
103 #ifdef DEBUG
104 static void show_terminal_settings(struct termios *);
105 #endif
106
handle_ctrlc(int sig)107 static void handle_ctrlc(int sig)
108 {
109 /* Reinstall the handler, and signal break flag */
110 signal(SIGINT,handle_ctrlc);
111 recv_sig = SIGINT;
112 }
113
handle_sigwinch(int sig)114 static void handle_sigwinch(int sig)
115 {
116 recv_sig = SIGWINCH;
117 }
118
usage(char * pname)119 static void usage(char *pname)
120 {
121 fprintf(stderr, "Usage: %s [-h|-F] [pipe_name|pipe_dir/]\n", pname);
122 fprintf(stderr, "\t-h\tThis help text.\n");
123 fprintf(stderr, "\t-F\tForce connection even though pipe is locked by other to_erl process.\n");
124 }
125
main(int argc,char ** argv)126 int main(int argc, char **argv)
127 {
128 char FIFO1[FILENAME_MAX], FIFO2[FILENAME_MAX];
129 int i, len, wfd, rfd;
130 fd_set readfds;
131 char buf[BUFSIZ];
132 char pipename[FILENAME_MAX];
133 int pipeIx = 1;
134 int force_lock = 0;
135 int got_some = 0;
136
137 if (argc >= 2 && argv[1][0]=='-') {
138 switch (argv[1][1]) {
139 case 'h':
140 usage(argv[0]);
141 exit(1);
142 case 'F':
143 force_lock = 1;
144 break;
145 default:
146 fprintf(stderr,"Invalid option '%s'\n",argv[1]);
147 exit(1);
148 }
149 pipeIx = 2;
150 }
151
152 #ifdef DEBUG
153 fprintf(stderr, "%s: pid is : %d\n", argv[0], (int)getpid());
154 #endif
155
156 strn_cpy(pipename, sizeof(pipename),
157 (argv[pipeIx] ? argv[pipeIx] : PIPE_DIR));
158
159 if(*pipename && pipename[strlen(pipename)-1] == '/') {
160 /* The user wishes us to find a pipe name in the specified */
161 /* directory */
162 int highest_pipe_num = 0;
163 DIR *dirp;
164 struct dirent *direntp;
165
166 dirp = opendir(pipename);
167 if(!dirp) {
168 fprintf(stderr, "Can't access pipe directory %s: %s\n", pipename, strerror(errno));
169 exit(1);
170 }
171
172 /* Check the directory for existing pipes */
173
174 while((direntp=readdir(dirp)) != NULL) {
175 if(strncmp(direntp->d_name,PIPE_STUBNAME,PIPE_STUBLEN)==0) {
176 int num = atoi(direntp->d_name+PIPE_STUBLEN+1);
177 if(num > highest_pipe_num)
178 highest_pipe_num = num;
179 }
180 }
181 closedir(dirp);
182 strn_catf(pipename, sizeof(pipename), (highest_pipe_num?"%s.%d":"%s"),
183 PIPE_STUBNAME, highest_pipe_num);
184 } /* if */
185
186 /* read FIFO */
187 sn_printf(FIFO1,sizeof(FIFO1),"%s.r",pipename);
188 /* write FIFO */
189 sn_printf(FIFO2,sizeof(FIFO2),"%s.w",pipename);
190
191 /* Check that nobody is running to_erl on this pipe already */
192 if ((wfd = open (FIFO1, O_WRONLY|DONT_BLOCK_PLEASE, 0)) >= 0) {
193 /* Open as server succeeded -- to_erl is already running! */
194 close(wfd);
195 fprintf(stderr, "Another to_erl process already attached to pipe "
196 "%s.\n", pipename);
197 if (force_lock) {
198 fprintf(stderr, "But we proceed anyway by force (-F).\n");
199 }
200 else {
201 exit(1);
202 }
203 }
204
205 if ((rfd = open (FIFO1, O_RDONLY|DONT_BLOCK_PLEASE, 0)) < 0) {
206 #ifdef DEBUG
207 fprintf(stderr, "Could not open FIFO %s for reading.\n", FIFO1);
208 #endif
209 fprintf(stderr, "No running Erlang on pipe %s: %s\n", pipename, strerror(errno));
210 exit(1);
211 }
212 #ifdef DEBUG
213 fprintf(stderr, "to_erl: %s opened for reading\n", FIFO1);
214 #endif
215
216 if ((wfd = open (FIFO2, O_WRONLY|DONT_BLOCK_PLEASE, 0)) < 0) {
217 #ifdef DEBUG
218 fprintf(stderr, "Could not open FIFO %s for writing.\n", FIFO2);
219 #endif
220 fprintf(stderr, "No running Erlang on pipe %s: %s\n", pipename, strerror(errno));
221 close(rfd);
222 exit(1);
223 }
224 #ifdef DEBUG
225 fprintf(stderr, "to_erl: %s opened for writing\n", FIFO2);
226 #endif
227
228 fprintf(stderr, "Attaching to %s (^D to exit)\n\n", pipename);
229
230 /* Set break handler to our handler */
231 signal(SIGINT,handle_ctrlc);
232
233 /*
234 * Save the current state of the terminal, and set raw mode.
235 */
236 if (tcgetattr(0, &tty_rmode) , 0) {
237 fprintf(stderr, "Cannot get terminals current mode\n");
238 exit(-1);
239 }
240 tty_smode = tty_rmode;
241 tty_eof = '\004'; /* Ctrl+D to exit */
242 #ifdef DEBUG
243 show_terminal_settings(&tty_rmode);
244 #endif
245 tty_smode.c_iflag =
246 1*BRKINT |/*Signal interrupt on break.*/
247 1*IGNPAR |/*Ignore characters with parity errors.*/
248 0;
249
250 #if 0
251 0*IGNBRK |/*Ignore break condition.*/
252 0*PARMRK |/*Mark parity errors.*/
253 0*INPCK |/*Enable input parity check.*/
254 0*INLCR |/*Map NL to CR on input.*/
255 0*IGNCR |/*Ignore CR.*/
256 0*ICRNL |/*Map CR to NL on input.*/
257 0*IUCLC |/*Map upper-case to lower-case on input.*/
258 0*IXON |/*Enable start/stop output control.*/
259 0*IXANY |/*Enable any character to restart output.*/
260 0*IXOFF |/*Enable start/stop input control.*/
261 0*IMAXBEL|/*Echo BEL on input line too long.*/
262 #endif
263
264 tty_smode.c_oflag =
265 1*OPOST |/*Post-process output.*/
266 1*ONLCR |/*Map NL to CR-NL on output.*/
267 #ifdef XTABS
268 1*XTABS |/*Expand tabs to spaces. (Linux)*/
269 #endif
270 #ifdef OXTABS
271 1*OXTABS |/*Expand tabs to spaces. (FreeBSD)*/
272 #endif
273 #ifdef NL0
274 1*NL0 |/*Select newline delays*/
275 #endif
276 #ifdef CR0
277 1*CR0 |/*Select carriage-return delays*/
278 #endif
279 #ifdef TAB0
280 1*TAB0 |/*Select horizontal tab delays*/
281 #endif
282 #ifdef BS0
283 1*BS0 |/*Select backspace delays*/
284 #endif
285 #ifdef VT0
286 1*VT0 |/*Select vertical tab delays*/
287 #endif
288 #ifdef FF0
289 1*FF0 |/*Select form feed delays*/
290 #endif
291 0;
292
293 #if 0
294 0*OLCUC |/*Map lower case to upper on output.*/
295 0*OCRNL |/*Map CR to NL on output.*/
296 0*ONOCR |/*No CR output at column 0.*/
297 0*ONLRET |/*NL performs CR function.*/
298 0*OFILL |/*Use fill characters for delay.*/
299 0*OFDEL |/*Fill is DEL, else NULL.*/
300 0*NL1 |
301 0*CR1 |
302 0*CR2 |
303 0*CR3 |
304 0*TAB1 |
305 0*TAB2 |
306 0*TAB3 |/*Expand tabs to spaces.*/
307 0*BS1 |
308 0*VT1 |
309 0*FF1 |
310 #endif
311
312 /* JALI: removed setting the tty_smode.c_cflag flags, since this is not */
313 /* advisable if this is a *real* terminal, such as the console. In fact */
314 /* this may hang the entire machine, deep, deep down (signalling break */
315 /* or toggling the abort switch doesn't help) */
316
317 tty_smode.c_lflag =
318 0;
319
320 #if 0
321 0*ISIG |/*Enable signals.*/
322 0*ICANON |/*Canonical input (erase and kill processing).*/
323 0*XCASE |/*Canonical upper/lower presentation.*/
324 0*ECHO |/*Enable echo.*/
325 0*ECHOE |/*Echo erase character as BS-SP-BS.*/
326 0*ECHOK |/*Echo NL after kill character.*/
327 0*ECHONL |/*Echo NL.*/
328 0*NOFLSH |/*Disable flush after interrupt or quit.*/
329 0*TOSTOP |/*Send SIGTTOU for background output.*/
330 0*ECHOCTL|/*Echo control characters as ^char, delete as ^?.*/
331 0*ECHOPRT|/*Echo erase character as character erased.*/
332 0*ECHOKE |/*BS-SP-BS erase entire line on line kill.*/
333 0*FLUSHO |/*Output is being flushed.*/
334 0*PENDIN |/*Retype pending input at next read or input character.*/
335 0*IEXTEN |/*Enable extended (implementation-defined) functions.*/
336 #endif
337
338 tty_smode.c_cc[VMIN] =0;/* Note that VMIN is the same as VEOF! */
339 tty_smode.c_cc[VTIME] =0;/* Note that VTIME is the same as VEOL! */
340 tty_smode.c_cc[VINTR] =3;
341
342 tcsetattr(0, TCSADRAIN, &tty_smode);
343
344 #ifdef DEBUG
345 show_terminal_settings(&tty_smode);
346 #endif
347 /*
348 * "Write a ^L to the FIFO which causes the other end to redisplay
349 * the input line."
350 * This does not seem to work as was intended in old comment above.
351 * However, this control character is now (R12B-3) used by run_erl
352 * to trigger the version handshaking between to_erl and run_erl
353 * at the start of every new to_erl-session.
354 */
355
356 if (write(wfd, "\014", 1) < 0) {
357 fprintf(stderr, "Error in writing ^L to FIFO.\n");
358 }
359
360 /*
361 * read and write
362 */
363 while (1) {
364 FD_ZERO(&readfds);
365 FD_SET(0, &readfds);
366 FD_SET(rfd, &readfds);
367 if (select(rfd + 1, &readfds, NULL, NULL, NULL) < 0) {
368 if (recv_sig) {
369 FD_ZERO(&readfds);
370 }
371 else {
372 fprintf(stderr, "Error in select.\n");
373 break;
374 }
375 }
376 len = 0;
377
378 /*
379 * Read from terminal and write to FIFO
380 */
381 if (recv_sig) {
382 switch (recv_sig) {
383 case SIGINT:
384 fprintf(stderr, "[Break]\n\r");
385 buf[0] = '\003';
386 len = 1;
387 break;
388 case SIGWINCH:
389 len = window_size_seq(buf,sizeof(buf));
390 break;
391 default:
392 fprintf(stderr,"Unexpected signal: %u\n",recv_sig);
393 }
394 recv_sig = 0;
395 }
396 else if (FD_ISSET(0, &readfds)) {
397 len = read(0, buf, sizeof(buf));
398 if (len <= 0) {
399 close(rfd);
400 close(wfd);
401 if (len < 0) {
402 fprintf(stderr, "Error in reading from stdin.\n");
403 } else {
404 fprintf(stderr, "[EOF]\n\r");
405 }
406 break;
407 }
408 /* check if there is an eof character in input */
409 for (i = 0; i < len && buf[i] != tty_eof; i++);
410 if (buf[i] == tty_eof) {
411 fprintf(stderr, "[Quit]\n\r");
412 break;
413 }
414 }
415
416 if (len) {
417 #ifdef DEBUG
418 write_all(1, buf, len);
419 #endif
420 if (write_all(wfd, buf, len) != len) {
421 fprintf(stderr, "Error in writing to FIFO.\n");
422 close(rfd);
423 close(wfd);
424 break;
425 }
426 STATUS("\" OK\r\n");
427 }
428
429 /*
430 * Read from FIFO, write to terminal.
431 */
432 if (FD_ISSET(rfd, &readfds)) {
433 STATUS("FIFO read: ");
434 len = read(rfd, buf, BUFSIZ);
435 if (len < 0 && errno == EAGAIN) {
436 /*
437 * No data this time, but the writing end of the FIFO is still open.
438 * Do nothing.
439 */
440 ;
441 } else if (len <= 0) {
442 /*
443 * Either an error or end of file. In either case, break out
444 * of the loop.
445 */
446 close(rfd);
447 close(wfd);
448 if (len < 0) {
449 fprintf(stderr, "Error in reading from FIFO.\n");
450 } else
451 fprintf(stderr, "[End]\n\r");
452 break;
453 } else {
454 if (!got_some) {
455 if ((len=version_handshake(buf,len,wfd)) < 0) {
456 close(rfd);
457 close(wfd);
458 break;
459 }
460 if (protocol_ver >= 1) {
461 /* Tell run_erl size of terminal window */
462 signal(SIGWINCH, handle_sigwinch);
463 raise(SIGWINCH);
464 }
465 got_some = 1;
466 }
467
468 /*
469 * We successfully read at least one character. Write what we got.
470 */
471 STATUS("Terminal write: \"");
472 if (write_all(1, buf, len) != len) {
473 fprintf(stderr, "Error in writing to terminal.\n");
474 close(rfd);
475 close(wfd);
476 break;
477 }
478 STATUS("\" OK\r\n");
479 }
480 }
481 }
482
483 /*
484 * Reset terminal characterstics
485 * XXX
486 */
487 tcsetattr(0, TCSADRAIN, &tty_rmode);
488 return 0;
489 }
490
491 /* Call write() until entire buffer has been written or error.
492 * Return len or -1.
493 */
write_all(int fd,const char * buf,int len)494 static int write_all(int fd, const char* buf, int len)
495 {
496 int left = len;
497 int written;
498 while (left) {
499 written = write(fd,buf,left);
500 if (written < 0) {
501 return -1;
502 }
503 left -= written;
504 buf += written;
505 }
506 return len;
507 }
508
window_size_seq(char * buf,size_t bufsz)509 static int window_size_seq(char* buf, size_t bufsz)
510 {
511 #ifdef TIOCGWINSZ
512 struct winsize ws;
513 static const char prefix[] = "\033_";
514 static const char suffix[] = "\033\\";
515 /* This Esc sequence is called "Application Program Command"
516 and seems suitable to use for our own customized stuff. */
517
518 if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == 0) {
519 int len = sn_printf(buf, bufsz, "%swinsize=%u,%u%s",
520 prefix, ws.ws_col, ws.ws_row, suffix);
521 return len;
522 }
523 #endif /* TIOCGWINSZ */
524 return 0;
525 }
526
527 /* to_erl run_erl
528 * | |
529 * |---------- '\014' -------->| (session start)
530 * | |
531 * |<---- "[run_erl v1-0]" ----| (version interval)
532 * | |
533 * |--- Esc_"version=1"Esc\ -->| (common version)
534 * | |
535 */
version_handshake(char * buf,int len,int wfd)536 static int version_handshake(char* buf, int len, int wfd)
537 {
538 unsigned re_high=0, re_low;
539 char *end = find_str(buf,len,"]\n");
540
541 if (end && sscanf(buf,"[run_erl v%u-%u",&re_high,&re_low)==2) {
542 char wbuf[30];
543 int wlen;
544
545 if (re_low > RUN_ERL_HI_VER || re_high < RUN_ERL_LO_VER) {
546 fprintf(stderr,"Incompatible versions: to_erl=v%u-%u run_erl=v%u-%u\n",
547 RUN_ERL_HI_VER, RUN_ERL_LO_VER, re_high, re_low);
548 return -1;
549 }
550 /* Choose highest common version */
551 protocol_ver = re_high < RUN_ERL_HI_VER ? re_high : RUN_ERL_HI_VER;
552
553 wlen = sn_printf(wbuf, sizeof(wbuf), "\033_version=%u\033\\",
554 protocol_ver);
555 if (write_all(wfd, wbuf, wlen) < 0) {
556 fprintf(stderr,"Failed to send version handshake\n");
557 return -1;
558 }
559 end += 2;
560 len -= (end-buf);
561 memmove(buf,end,len);
562
563 }
564 else { /* we assume old run_erl without version handshake */
565 protocol_ver = 0;
566 }
567
568 if (re_high != RUN_ERL_HI_VER) {
569 fprintf(stderr,"run_erl has different version, "
570 "using common protocol level %u\n", protocol_ver);
571 }
572
573 return len;
574 }
575
576
577 #ifdef DEBUG
578 #define S(x) ((x) > 0 ? 1 : 0)
579
show_terminal_settings(struct termios * t)580 static void show_terminal_settings(struct termios *t)
581 {
582 fprintf(stderr,"c_iflag:\n");
583 fprintf(stderr,"Signal interrupt on break: BRKINT %d\n", S(t->c_iflag & BRKINT));
584 fprintf(stderr,"Map CR to NL on input: ICRNL %d\n", S(t->c_iflag & ICRNL));
585 fprintf(stderr,"Ignore break condition: IGNBRK %d\n", S(t->c_iflag & IGNBRK));
586 fprintf(stderr,"Ignore CR: IGNCR %d\n", S(t->c_iflag & IGNCR));
587 fprintf(stderr,"Ignore char with par. err's: IGNPAR %d\n", S(t->c_iflag & IGNPAR));
588 fprintf(stderr,"Map NL to CR on input: INLCR %d\n", S(t->c_iflag & INLCR));
589 fprintf(stderr,"Enable input parity check: INPCK %d\n", S(t->c_iflag & INPCK));
590 fprintf(stderr,"Strip character ISTRIP %d\n", S(t->c_iflag & ISTRIP));
591 fprintf(stderr,"Enable start/stop input ctrl IXOFF %d\n", S(t->c_iflag & IXOFF));
592 fprintf(stderr,"ditto output ctrl IXON %d\n", S(t->c_iflag & IXON));
593 fprintf(stderr,"Mark parity errors PARMRK %d\n", S(t->c_iflag & PARMRK));
594 fprintf(stderr,"\n");
595 fprintf(stderr,"c_oflag:\n");
596 fprintf(stderr,"Perform output processing OPOST %d\n", S(t->c_oflag & OPOST));
597 fprintf(stderr,"\n");
598 fprintf(stderr,"c_cflag:\n");
599 fprintf(stderr,"Ignore modem status lines CLOCAL %d\n", S(t->c_cflag & CLOCAL));
600 fprintf(stderr,"\n");
601 fprintf(stderr,"c_local:\n");
602 fprintf(stderr,"Enable echo ECHO %d\n", S(t->c_lflag & ECHO));
603 fprintf(stderr,"\n");
604 fprintf(stderr,"c_cc:\n");
605 fprintf(stderr,"c_cc[VEOF] %d\n", t->c_cc[VEOF]);
606 }
607 #endif
608