Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

608 lines (550 sloc) 16.771 kb
/*
* %CopyrightBegin%
*
* Copyright Ericsson AB 1996-2012. All Rights Reserved.
*
* The contents of this file are subject to the Erlang Public License,
* Version 1.1, (the "License"); you may not use this file except in
* compliance with the License. You should have received a copy of the
* Erlang Public License along with this software. If not, it can be
* retrieved online at http://www.erlang.org/.
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights 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 <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <dirent.h>
#include <signal.h>
#include <errno.h>
#ifdef HAVE_SYS_IOCTL_H
# include <sys/ioctl.h>
#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, TCSANOW, &tty_smode);
#ifdef DEBUG
show_terminal_settings(&tty_smode);
#endif
/*
* "Write a ^R 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 ^R 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, TCSANOW, &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
* | |
* |---------- '\022' -------->| (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
Jump to Line
Something went wrong with that request. Please try again.