/* * %CopyrightBegin% * * Copyright Ericsson AB 1996-2015. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * %CopyrightEnd% */ /* * Module: to_erl.c * * This module implements a process that opens two specified FIFOs, one * for reading and one for writing; reads from its stdin, and writes what * it has read to the write FIF0; reads from the read FIFO, and writes to * its stdout. * ________ _________ | |--<-- pipe.r (fifo1) --<--| | | to_erl | | run_erl | (parent) |________|-->-- pipe.w (fifo2) -->--|_________| ^ master pty | | slave pty ____V____ | | | "erl" | (child) |_________| */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_SYS_IOCTL_H # include #endif #include "run_erl.h" #include "safe_string.h" /* strn_cpy, strn_catf, sn_printf, etc. */ #if defined(O_NONBLOCK) # define DONT_BLOCK_PLEASE O_NONBLOCK #else # define DONT_BLOCK_PLEASE O_NDELAY # if !defined(EAGAIN) # define EAGAIN -3898734 # endif #endif #ifdef HAVE_STRERROR # define STRERROR(x) strerror(x) #else # define STRERROR(x) "" #endif #define noDEBUG #define PIPE_DIR "/tmp/" #define PIPE_STUBNAME "erlang.pipe" #define PIPE_STUBLEN strlen(PIPE_STUBNAME) #ifdef DEBUG #define STATUS(s) { fprintf(stderr, (s)); fflush(stderr); } #else #define STATUS(s) #endif #ifndef FILENAME_MAX #define FILENAME_MAX 250 #endif static struct termios tty_smode, tty_rmode; static int tty_eof = 0; static int recv_sig = 0; static int protocol_ver = RUN_ERL_LO_VER; /* assume lowest to begin with */ static int write_all(int fd, const char* buf, int len); static int window_size_seq(char* buf, size_t bufsz); static int version_handshake(char* buf, int len, int wfd); #ifdef DEBUG static void show_terminal_settings(struct termios *); #endif static void handle_ctrlc(int sig) { /* Reinstall the handler, and signal break flag */ signal(SIGINT,handle_ctrlc); recv_sig = SIGINT; } static void handle_sigwinch(int sig) { recv_sig = SIGWINCH; } static void usage(char *pname) { fprintf(stderr, "Usage: %s [-h|-F] [pipe_name|pipe_dir/]\n", pname); fprintf(stderr, "\t-h\tThis help text.\n"); fprintf(stderr, "\t-F\tForce connection even though pipe is locked by other to_erl process.\n"); } int main(int argc, char **argv) { char FIFO1[FILENAME_MAX], FIFO2[FILENAME_MAX]; int i, len, wfd, rfd; fd_set readfds; char buf[BUFSIZ]; char pipename[FILENAME_MAX]; int pipeIx = 1; int force_lock = 0; int got_some = 0; if (argc >= 2 && argv[1][0]=='-') { switch (argv[1][1]) { case 'h': usage(argv[0]); exit(1); case 'F': force_lock = 1; break; default: fprintf(stderr,"Invalid option '%s'\n",argv[1]); exit(1); } pipeIx = 2; } #ifdef DEBUG fprintf(stderr, "%s: pid is : %d\n", argv[0], (int)getpid()); #endif strn_cpy(pipename, sizeof(pipename), (argv[pipeIx] ? argv[pipeIx] : PIPE_DIR)); if(*pipename && pipename[strlen(pipename)-1] == '/') { /* The user wishes us to find a pipe name in the specified */ /* directory */ int highest_pipe_num = 0; DIR *dirp; struct dirent *direntp; dirp = opendir(pipename); if(!dirp) { fprintf(stderr, "Can't access pipe directory %s: %s\n", pipename, strerror(errno)); exit(1); } /* Check the directory for existing pipes */ while((direntp=readdir(dirp)) != NULL) { if(strncmp(direntp->d_name,PIPE_STUBNAME,PIPE_STUBLEN)==0) { int num = atoi(direntp->d_name+PIPE_STUBLEN+1); if(num > highest_pipe_num) highest_pipe_num = num; } } closedir(dirp); strn_catf(pipename, sizeof(pipename), (highest_pipe_num?"%s.%d":"%s"), PIPE_STUBNAME, highest_pipe_num); } /* if */ /* read FIFO */ sn_printf(FIFO1,sizeof(FIFO1),"%s.r",pipename); /* write FIFO */ sn_printf(FIFO2,sizeof(FIFO2),"%s.w",pipename); /* Check that nobody is running to_erl on this pipe already */ if ((wfd = open (FIFO1, O_WRONLY|DONT_BLOCK_PLEASE, 0)) >= 0) { /* Open as server succeeded -- to_erl is already running! */ close(wfd); fprintf(stderr, "Another to_erl process already attached to pipe " "%s.\n", pipename); if (force_lock) { fprintf(stderr, "But we proceed anyway by force (-F).\n"); } else { exit(1); } } if ((rfd = open (FIFO1, O_RDONLY|DONT_BLOCK_PLEASE, 0)) < 0) { #ifdef DEBUG fprintf(stderr, "Could not open FIFO %s for reading.\n", FIFO1); #endif fprintf(stderr, "No running Erlang on pipe %s: %s\n", pipename, strerror(errno)); exit(1); } #ifdef DEBUG fprintf(stderr, "to_erl: %s opened for reading\n", FIFO1); #endif if ((wfd = open (FIFO2, O_WRONLY|DONT_BLOCK_PLEASE, 0)) < 0) { #ifdef DEBUG fprintf(stderr, "Could not open FIFO %s for writing.\n", FIFO2); #endif fprintf(stderr, "No running Erlang on pipe %s: %s\n", pipename, strerror(errno)); close(rfd); exit(1); } #ifdef DEBUG fprintf(stderr, "to_erl: %s opened for writing\n", FIFO2); #endif fprintf(stderr, "Attaching to %s (^D to exit)\n\n", pipename); /* Set break handler to our handler */ signal(SIGINT,handle_ctrlc); /* * Save the current state of the terminal, and set raw mode. */ if (tcgetattr(0, &tty_rmode) , 0) { fprintf(stderr, "Cannot get terminals current mode\n"); exit(-1); } tty_smode = tty_rmode; tty_eof = '\004'; /* Ctrl+D to exit */ #ifdef DEBUG show_terminal_settings(&tty_rmode); #endif tty_smode.c_iflag = 1*BRKINT |/*Signal interrupt on break.*/ 1*IGNPAR |/*Ignore characters with parity errors.*/ 1*ISTRIP |/*Strip character.*/ 0; #if 0 0*IGNBRK |/*Ignore break condition.*/ 0*PARMRK |/*Mark parity errors.*/ 0*INPCK |/*Enable input parity check.*/ 0*INLCR |/*Map NL to CR on input.*/ 0*IGNCR |/*Ignore CR.*/ 0*ICRNL |/*Map CR to NL on input.*/ 0*IUCLC |/*Map upper-case to lower-case on input.*/ 0*IXON |/*Enable start/stop output control.*/ 0*IXANY |/*Enable any character to restart output.*/ 0*IXOFF |/*Enable start/stop input control.*/ 0*IMAXBEL|/*Echo BEL on input line too long.*/ #endif tty_smode.c_oflag = 1*OPOST |/*Post-process output.*/ 1*ONLCR |/*Map NL to CR-NL on output.*/ #ifdef XTABS 1*XTABS |/*Expand tabs to spaces. (Linux)*/ #endif #ifdef OXTABS 1*OXTABS |/*Expand tabs to spaces. (FreeBSD)*/ #endif #ifdef NL0 1*NL0 |/*Select newline delays*/ #endif #ifdef CR0 1*CR0 |/*Select carriage-return delays*/ #endif #ifdef TAB0 1*TAB0 |/*Select horizontal tab delays*/ #endif #ifdef BS0 1*BS0 |/*Select backspace delays*/ #endif #ifdef VT0 1*VT0 |/*Select vertical tab delays*/ #endif #ifdef FF0 1*FF0 |/*Select form feed delays*/ #endif 0; #if 0 0*OLCUC |/*Map lower case to upper on output.*/ 0*OCRNL |/*Map CR to NL on output.*/ 0*ONOCR |/*No CR output at column 0.*/ 0*ONLRET |/*NL performs CR function.*/ 0*OFILL |/*Use fill characters for delay.*/ 0*OFDEL |/*Fill is DEL, else NULL.*/ 0*NL1 | 0*CR1 | 0*CR2 | 0*CR3 | 0*TAB1 | 0*TAB2 | 0*TAB3 |/*Expand tabs to spaces.*/ 0*BS1 | 0*VT1 | 0*FF1 | #endif /* JALI: removed setting the tty_smode.c_cflag flags, since this is not */ /* advisable if this is a *real* terminal, such as the console. In fact */ /* this may hang the entire machine, deep, deep down (signalling break */ /* or toggling the abort switch doesn't help) */ tty_smode.c_lflag = 0; #if 0 0*ISIG |/*Enable signals.*/ 0*ICANON |/*Canonical input (erase and kill processing).*/ 0*XCASE |/*Canonical upper/lower presentation.*/ 0*ECHO |/*Enable echo.*/ 0*ECHOE |/*Echo erase character as BS-SP-BS.*/ 0*ECHOK |/*Echo NL after kill character.*/ 0*ECHONL |/*Echo NL.*/ 0*NOFLSH |/*Disable flush after interrupt or quit.*/ 0*TOSTOP |/*Send SIGTTOU for background output.*/ 0*ECHOCTL|/*Echo control characters as ^char, delete as ^?.*/ 0*ECHOPRT|/*Echo erase character as character erased.*/ 0*ECHOKE |/*BS-SP-BS erase entire line on line kill.*/ 0*FLUSHO |/*Output is being flushed.*/ 0*PENDIN |/*Retype pending input at next read or input character.*/ 0*IEXTEN |/*Enable extended (implementation-defined) functions.*/ #endif tty_smode.c_cc[VMIN] =0;/* Note that VMIN is the same as VEOF! */ tty_smode.c_cc[VTIME] =0;/* Note that VTIME is the same as VEOL! */ tty_smode.c_cc[VINTR] =3; tcsetattr(0, TCSADRAIN, &tty_smode); #ifdef DEBUG show_terminal_settings(&tty_smode); #endif /* * "Write a ^L to the FIFO which causes the other end to redisplay * the input line." * This does not seem to work as was intended in old comment above. * However, this control character is now (R12B-3) used by run_erl * to trigger the version handshaking between to_erl and run_erl * at the start of every new to_erl-session. */ if (write(wfd, "\014", 1) < 0) { fprintf(stderr, "Error in writing ^L to FIFO.\n"); } /* * read and write */ while (1) { FD_ZERO(&readfds); FD_SET(0, &readfds); FD_SET(rfd, &readfds); if (select(rfd + 1, &readfds, NULL, NULL, NULL) < 0) { if (recv_sig) { FD_ZERO(&readfds); } else { fprintf(stderr, "Error in select.\n"); break; } } len = 0; /* * Read from terminal and write to FIFO */ if (recv_sig) { switch (recv_sig) { case SIGINT: fprintf(stderr, "[Break]\n\r"); buf[0] = '\003'; len = 1; break; case SIGWINCH: len = window_size_seq(buf,sizeof(buf)); break; default: fprintf(stderr,"Unexpected signal: %u\n",recv_sig); } recv_sig = 0; } else if (FD_ISSET(0, &readfds)) { len = read(0, buf, sizeof(buf)); if (len <= 0) { close(rfd); close(wfd); if (len < 0) { fprintf(stderr, "Error in reading from stdin.\n"); } else { fprintf(stderr, "[EOF]\n\r"); } break; } /* check if there is an eof character in input */ for (i = 0; i < len && buf[i] != tty_eof; i++); if (buf[i] == tty_eof) { fprintf(stderr, "[Quit]\n\r"); break; } } if (len) { #ifdef DEBUG if(write(1, buf, len)); #endif if (write_all(wfd, buf, len) != len) { fprintf(stderr, "Error in writing to FIFO.\n"); close(rfd); close(wfd); break; } STATUS("\" OK\r\n"); } /* * Read from FIFO, write to terminal. */ if (FD_ISSET(rfd, &readfds)) { STATUS("FIFO read: "); len = read(rfd, buf, BUFSIZ); if (len < 0 && errno == EAGAIN) { /* * No data this time, but the writing end of the FIFO is still open. * Do nothing. */ ; } else if (len <= 0) { /* * Either an error or end of file. In either case, break out * of the loop. */ close(rfd); close(wfd); if (len < 0) { fprintf(stderr, "Error in reading from FIFO.\n"); } else fprintf(stderr, "[End]\n\r"); break; } else { if (!got_some) { if ((len=version_handshake(buf,len,wfd)) < 0) { close(rfd); close(wfd); break; } if (protocol_ver >= 1) { /* Tell run_erl size of terminal window */ signal(SIGWINCH, handle_sigwinch); raise(SIGWINCH); } got_some = 1; } /* * We successfully read at least one character. Write what we got. */ STATUS("Terminal write: \""); if (write_all(1, buf, len) != len) { fprintf(stderr, "Error in writing to terminal.\n"); close(rfd); close(wfd); break; } STATUS("\" OK\r\n"); } } } /* * Reset terminal characterstics * XXX */ tcsetattr(0, TCSADRAIN, &tty_rmode); return 0; } /* Call write() until entire buffer has been written or error. * Return len or -1. */ static int write_all(int fd, const char* buf, int len) { int left = len; int written; while (left) { written = write(fd,buf,left); if (written < 0) { return -1; } left -= written; buf += written; } return len; } static int window_size_seq(char* buf, size_t bufsz) { #ifdef TIOCGWINSZ struct winsize ws; static const char prefix[] = "\033_"; static const char suffix[] = "\033\\"; /* This Esc sequence is called "Application Program Command" and seems suitable to use for our own customized stuff. */ if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == 0) { int len = sn_printf(buf, bufsz, "%swinsize=%u,%u%s", prefix, ws.ws_col, ws.ws_row, suffix); return len; } #endif /* TIOCGWINSZ */ return 0; } /* to_erl run_erl * | | * |---------- '\014' -------->| (session start) * | | * |<---- "[run_erl v1-0]" ----| (version interval) * | | * |--- Esc_"version=1"Esc\ -->| (common version) * | | */ static int version_handshake(char* buf, int len, int wfd) { unsigned re_high=0, re_low; char *end = find_str(buf,len,"]\n"); if (end && sscanf(buf,"[run_erl v%u-%u",&re_high,&re_low)==2) { char wbuf[30]; int wlen; if (re_low > RUN_ERL_HI_VER || re_high < RUN_ERL_LO_VER) { fprintf(stderr,"Incompatible versions: to_erl=v%u-%u run_erl=v%u-%u\n", RUN_ERL_HI_VER, RUN_ERL_LO_VER, re_high, re_low); return -1; } /* Choose highest common version */ protocol_ver = re_high < RUN_ERL_HI_VER ? re_high : RUN_ERL_HI_VER; wlen = sn_printf(wbuf, sizeof(wbuf), "\033_version=%u\033\\", protocol_ver); if (write_all(wfd, wbuf, wlen) < 0) { fprintf(stderr,"Failed to send version handshake\n"); return -1; } end += 2; len -= (end-buf); memmove(buf,end,len); } else { /* we assume old run_erl without version handshake */ protocol_ver = 0; } if (re_high != RUN_ERL_HI_VER) { fprintf(stderr,"run_erl has different version, " "using common protocol level %u\n", protocol_ver); } return len; } #ifdef DEBUG #define S(x) ((x) > 0 ? 1 : 0) static void show_terminal_settings(struct termios *t) { fprintf(stderr,"c_iflag:\n"); fprintf(stderr,"Signal interrupt on break: BRKINT %d\n", S(t->c_iflag & BRKINT)); fprintf(stderr,"Map CR to NL on input: ICRNL %d\n", S(t->c_iflag & ICRNL)); fprintf(stderr,"Ignore break condition: IGNBRK %d\n", S(t->c_iflag & IGNBRK)); fprintf(stderr,"Ignore CR: IGNCR %d\n", S(t->c_iflag & IGNCR)); fprintf(stderr,"Ignore char with par. err's: IGNPAR %d\n", S(t->c_iflag & IGNPAR)); fprintf(stderr,"Map NL to CR on input: INLCR %d\n", S(t->c_iflag & INLCR)); fprintf(stderr,"Enable input parity check: INPCK %d\n", S(t->c_iflag & INPCK)); fprintf(stderr,"Strip character ISTRIP %d\n", S(t->c_iflag & ISTRIP)); fprintf(stderr,"Enable start/stop input ctrl IXOFF %d\n", S(t->c_iflag & IXOFF)); fprintf(stderr,"ditto output ctrl IXON %d\n", S(t->c_iflag & IXON)); fprintf(stderr,"Mark parity errors PARMRK %d\n", S(t->c_iflag & PARMRK)); fprintf(stderr,"\n"); fprintf(stderr,"c_oflag:\n"); fprintf(stderr,"Perform output processing OPOST %d\n", S(t->c_oflag & OPOST)); fprintf(stderr,"\n"); fprintf(stderr,"c_cflag:\n"); fprintf(stderr,"Ignore modem status lines CLOCAL %d\n", S(t->c_cflag & CLOCAL)); fprintf(stderr,"\n"); fprintf(stderr,"c_local:\n"); fprintf(stderr,"Enable echo ECHO %d\n", S(t->c_lflag & ECHO)); fprintf(stderr,"\n"); fprintf(stderr,"c_cc:\n"); fprintf(stderr,"c_cc[VEOF] %d\n", t->c_cc[VEOF]); } #endif