diff options
author | Sebastian Huber <sebastian.huber@embedded-brains.de> | 2014-09-18 09:49:32 +0200 |
---|---|---|
committer | Sebastian Huber <sebastian.huber@embedded-brains.de> | 2014-09-18 09:49:32 +0200 |
commit | a197a48f8fb970046585b636149c7670bfe65108 (patch) | |
tree | a423f2eafa2141b9e4859230dee3cb5edf5eae75 /rtemsbsd/telnetd/pty.c | |
parent | e96e008c66bb2cf7c80036d90b9e059d2bc8a930 (diff) |
telnetd: Import from RTEMS sources
Diffstat (limited to 'rtemsbsd/telnetd/pty.c')
-rw-r--r-- | rtemsbsd/telnetd/pty.c | 654 |
1 files changed, 654 insertions, 0 deletions
diff --git a/rtemsbsd/telnetd/pty.c b/rtemsbsd/telnetd/pty.c new file mode 100644 index 00000000..30bfe989 --- /dev/null +++ b/rtemsbsd/telnetd/pty.c @@ -0,0 +1,654 @@ +/* + * /dev/ptyXX (Support for pseudo-terminals) + * + * Original Author: Fernando RUIZ CASAS (fernando.ruiz@ctv.es) + * May 2001 + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * Till Straumann <strauman@slac.stanford.edu> + * + * - converted into a loadable module + * - NAWS support / ioctls for querying/setting the window + * size added. + * - don't delete the running task when the connection + * is closed. Rather let 'read()' return a 0 count so + * they may cleanup. Some magic hack works around termios + * limitation. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#define DEBUG_WH (1<<0) +#define DEBUG_DETAIL (1<<1) + +/* #define DEBUG DEBUG_WH */ + +/*-----------------------------------------*/ +#include <termios.h> +#include <rtems/termiostypes.h> +#include <sys/ttycom.h> +#include <rtems.h> +#include <rtems/libio.h> +#include <rtems/bspIo.h> +#include <errno.h> +#include <sys/socket.h> +/*-----------------------------------------*/ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +/*-----------------------------------------*/ +#define IAC_ESC 255 +#define IAC_DONT 254 +#define IAC_DO 253 +#define IAC_WONT 252 +#define IAC_WILL 251 +#define IAC_SB 250 +#define IAC_GA 249 +#define IAC_EL 248 +#define IAC_EC 247 +#define IAC_AYT 246 +#define IAC_AO 245 +#define IAC_IP 244 +#define IAC_BRK 243 +#define IAC_DMARK 242 +#define IAC_NOP 241 +#define IAC_SE 240 +#define IAC_EOR 239 + +#define SB_MAX 16 + +extern int rtems_telnetd_maximum_ptys; + +struct pty_tt; +typedef struct pty_tt pty_t; + +struct pty_tt { + char *devname; + struct rtems_termios_tty *ttyp; + tcflag_t c_cflag; + int opened; + int socket; + int last_cr; + unsigned iac_mode; + unsigned char sb_buf[SB_MAX]; + int sb_ind; + int width; + int height; +}; + + +static int telnet_pty_inited=FALSE; +static pty_t *telnet_ptys; + +static rtems_device_major_number pty_major; + +static +int send_iac(int minor,unsigned char mode,unsigned char option) +{ + unsigned char buf[3]; + + buf[0]=IAC_ESC; + buf[1]=mode; + buf[2]=option; + return write(telnet_ptys[minor].socket,buf,sizeof(buf)); +} + +/* This procedure returns the devname for a pty slot free. + * If not slot availiable (field socket>=0) + * then the socket argument is closed + */ + +char * telnet_get_pty(int socket) +{ + int ndx; + + if (telnet_pty_inited) { +#if 0 + if ( rtems_telnetd_maximum_ptys < 5 ) + rtems_telnetd_maximum_ptys = 5; + + telnet_ptys = malloc( rtems_telnetd_maximum_ptys * sizeof (pty_t) ); +#endif + if ( !telnet_ptys ) { + return NULL; + } + + for (ndx=0;ndx<rtems_telnetd_maximum_ptys;ndx++) { + + if (telnet_ptys[ndx].socket<0) { + struct timeval t; + /* set a long polling interval to save CPU time */ + t.tv_sec=2; + t.tv_usec=00000; + setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, &t, sizeof(t)); + telnet_ptys[ndx].socket=socket; + + /* inform the client that we will echo */ + send_iac(ndx, IAC_WILL, 1); + + return telnet_ptys[ndx].devname; + }; + }; + } + close(socket); + return NULL; +} + + +/*-----------------------------------------------------------*/ +/* + * The NVT terminal is negociated in PollRead and PollWrite + * with every BYTE sendded or received. + * A litle status machine in the pty_read_byte(int minor) + * + */ +static const char IAC_AYT_RSP[]="\r\nAYT? Yes, RTEMS-SHELL is here\r\n"; +static const char IAC_BRK_RSP[]="<*Break*>"; +static const char IAC_IP_RSP []="<*Interrupt*>"; + +static int +handleSB(pty_t *pty) +{ + switch (pty->sb_buf[0]) { + case 31: /* NAWS */ + pty->width = (pty->sb_buf[1]<<8) + pty->sb_buf[2]; + pty->height = (pty->sb_buf[3]<<8) + pty->sb_buf[4]; +#if DEBUG & DEBUG_WH + fprintf(stderr, + "Setting width/height to %ix%i\n", + pty->width, + pty->height); +#endif + break; + default: + break; + } + return 0; +} + +static int read_pty(int minor) +{ /* Characters written to the client side*/ + unsigned char value; + unsigned int omod; + int count; + int result; + pty_t *pty=telnet_ptys+minor; + + count=read(pty->socket,&value,sizeof(value)); + if (count<0) + return -1; + + if (count<1) { + /* Unfortunately, there is no way of passing an EOF + * condition through the termios driver. Hence, we + * resort to an ugly hack. Setting cindex>ccount + * causes the termios driver to return a read count + * of '0' which is what we want here. We leave + * 'errno' untouched. + */ + pty->ttyp->cindex=pty->ttyp->ccount+1; + return pty->ttyp->termios.c_cc[VEOF]; + }; + + omod=pty->iac_mode; + pty->iac_mode=0; + switch(omod & 0xff) { + case IAC_ESC: + switch(value) { + case IAC_ESC : + /* in case this is an ESC ESC sequence in SB mode */ + pty->iac_mode = omod>>8; + return IAC_ESC; + case IAC_DONT: + case IAC_DO : + case IAC_WONT: + case IAC_WILL: + pty->iac_mode=value; + return -1; + case IAC_SB : +#if DEBUG & DEBUG_DETAIL + printk("SB\n"); +#endif + pty->iac_mode=value; + pty->sb_ind=0; + return -100; + case IAC_GA : + return -1; + case IAC_EL : + return 0x03; /* Ctrl-C*/ + case IAC_EC : + return '\b'; + case IAC_AYT : + write(pty->socket,IAC_AYT_RSP,strlen(IAC_AYT_RSP)); + return -1; + case IAC_AO : + return -1; + case IAC_IP : + write(pty->socket,IAC_IP_RSP,strlen(IAC_IP_RSP)); + return -1; + case IAC_BRK : + write(pty->socket,IAC_BRK_RSP,strlen(IAC_BRK_RSP)); + return -1; + case IAC_DMARK: + return -2; + case IAC_NOP : + return -1; + case IAC_SE : +#if DEBUG & DEBUG_DETAIL + { + int i; + printk("SE"); + for (i=0; i<pty->sb_ind; i++) + printk(" %02x",pty->sb_buf[i]); + printk("\n"); + } +#endif + handleSB(pty); + return -101; + case IAC_EOR : + return -102; + default : + return -1; + }; + break; + + case IAC_SB: + pty->iac_mode=omod; + if (IAC_ESC==value) { + pty->iac_mode=(omod<<8)|value; + } else { + if (pty->sb_ind < SB_MAX) + pty->sb_buf[pty->sb_ind++]=value; + } + return -1; + + case IAC_WILL: + if (value==34){ + send_iac(minor,IAC_DONT, 34); /*LINEMODE*/ + send_iac(minor,IAC_DO , 1); /*ECHO */ + } else if (value==31) { + send_iac(minor,IAC_DO , 31); /*NAWS */ +#if DEBUG & DEBUG_DETAIL + printk("replied DO NAWS\n"); +#endif + } else { + send_iac(minor,IAC_DONT,value); + } + return -1; + case IAC_DONT: + return -1; + case IAC_DO : + if (value==3) { + send_iac(minor,IAC_WILL, 3); /* GO AHEAD*/ + } else if (value==1) { + send_iac(minor,IAC_WILL, 1); /* ECHO */ + } else { + send_iac(minor,IAC_WONT,value); + }; + return -1; + case IAC_WONT: + if (value==1) { + send_iac(minor,IAC_WILL, 1); + } else { /* ECHO */ + send_iac(minor,IAC_WONT,value); + } + return -1; + default: + if (value==IAC_ESC) { + pty->iac_mode=value; + return -1; + } else { + result=value; + if ( 0 + /* map CRLF to CR for symmetry */ + || ((value=='\n') && pty->last_cr) + /* map telnet CRNUL to CR down here */ + || ((value==0) && pty->last_cr) + ) result=-1; + pty->last_cr=(value=='\r'); + return result; + }; + }; + /* should never get here but keep compiler happy */ + return -1; +} + +/*-----------------------------------------------------------*/ +static int ptySetAttributes(int minor,const struct termios *t); +static int ptyPollInitialize(int major,int minor,void * arg) ; +static int ptyShutdown(int major,int minor,void * arg) ; +static ssize_t ptyPollWrite(int minor, const char * buf, size_t len) ; +static int ptyPollRead(int minor) ; +static const rtems_termios_callbacks * pty_get_termios_handlers(int polled) ; +/*-----------------------------------------------------------*/ +/* Set the 'Hardware' */ +/*-----------------------------------------------------------*/ +static int +ptySetAttributes(int minor,const struct termios *t) { + if (minor<rtems_telnetd_maximum_ptys) { + telnet_ptys[minor].c_cflag=t->c_cflag; + } else { + return -1; + }; + return 0; +} +/*-----------------------------------------------------------*/ +static int +ptyPollInitialize(int major,int minor,void * arg) { + rtems_libio_open_close_args_t * args = (rtems_libio_open_close_args_t*)arg; + struct termios t; + if (minor<rtems_telnetd_maximum_ptys) { + if (telnet_ptys[minor].socket<0) return -1; + telnet_ptys[minor].opened=TRUE; + telnet_ptys[minor].ttyp= (struct rtems_termios_tty *) args->iop->data1; + telnet_ptys[minor].iac_mode=0; + telnet_ptys[minor].sb_ind=0; + telnet_ptys[minor].width=0; + telnet_ptys[minor].height=0; + t.c_cflag=B9600|CS8;/* termios default */ + return ptySetAttributes(minor,&t); + } else { + return -1; + } +} +/*-----------------------------------------------------------*/ +static int +ptyShutdown(int major,int minor,void * arg) { + if (minor<rtems_telnetd_maximum_ptys) { + telnet_ptys[minor].opened=FALSE; + if (telnet_ptys[minor].socket>=0) close(telnet_ptys[minor].socket); + telnet_ptys[minor].socket=-1; + chown(telnet_ptys[minor].devname,2,0); + } else { + return -1; + } + return 0; +} +/*-----------------------------------------------------------*/ +/* Write Characters into pty device */ +/*-----------------------------------------------------------*/ +static ssize_t +ptyPollWrite(int minor, const char * buf, size_t len) { + size_t count; + if (minor<rtems_telnetd_maximum_ptys) { + if (telnet_ptys[minor].socket<0) + return -1; + count=write(telnet_ptys[minor].socket,buf,len); + } else { + count=-1; + } + return count; +} +/*-----------------------------------------------------------*/ +static int +ptyPollRead(int minor) { + int result; + + if (minor<rtems_telnetd_maximum_ptys) { + if (telnet_ptys[minor].socket<0) + return -1; + result=read_pty(minor); + return result; + } + return -1; +} +/*-----------------------------------------------------------*/ +/* pty_initialize + * + * This routine initializes the pty IO driver. + * + * Input parameters: NONE + * + * Output parameters: NONE + * + * Return values: + */ +/*-----------------------------------------------------------*/ +static +rtems_device_driver my_pty_initialize( + rtems_device_major_number major, + rtems_device_minor_number minor, + void *arg +) +{ + int ndx; + rtems_status_code status; + + if ( rtems_telnetd_maximum_ptys < 5 ) + rtems_telnetd_maximum_ptys = 5; + + telnet_ptys = malloc( rtems_telnetd_maximum_ptys * sizeof (pty_t) ); + + /* + * Set up ptys + */ + + for (ndx=0;ndx<rtems_telnetd_maximum_ptys;ndx++) { + telnet_ptys[ndx].devname = (char*)malloc(strlen("/dev/ptyXX")+1); + sprintf(telnet_ptys[ndx].devname,"/dev/pty%X",ndx); + telnet_ptys[ndx].ttyp = NULL; + telnet_ptys[ndx].c_cflag = CS8|B9600; + telnet_ptys[ndx].socket = -1; + telnet_ptys[ndx].opened = FALSE; + telnet_ptys[ndx].sb_ind = 0; + telnet_ptys[ndx].width = 0; + telnet_ptys[ndx].height = 0; + + } + + /* + * Register the devices + */ + for (ndx=0;ndx<rtems_telnetd_maximum_ptys;ndx++) { + status = rtems_io_register_name(telnet_ptys[ndx].devname, major, ndx); + if (status != RTEMS_SUCCESSFUL) + rtems_fatal_error_occurred(status); + chmod(telnet_ptys[ndx].devname,0660); + chown(telnet_ptys[ndx].devname,2,0); /* tty,root*/ + }; + printk("Device: /dev/pty%X../dev/pty%X (%d)pseudo-terminals registered.\n", + 0,rtems_telnetd_maximum_ptys-1,rtems_telnetd_maximum_ptys); + + return RTEMS_SUCCESSFUL; +} + +static int pty_do_finalize(void) +{ + int ndx; + rtems_status_code status; + + if ( !telnet_pty_inited ) + return 0; + + for (ndx=0;ndx<rtems_telnetd_maximum_ptys;ndx++) { + if (telnet_ptys[ndx].opened) { + fprintf(stderr, + "There are still opened PTY devices, unable to proceed\n"); + return -1; + } + } + if (RTEMS_SUCCESSFUL != rtems_io_unregister_driver(pty_major)) { + fprintf(stderr,"Unable to remove this driver\n"); + return -1; + } + for (ndx=0;ndx<rtems_telnetd_maximum_ptys;ndx++) { + /* rtems_io_register_name() actually creates a node in the filesystem + * (mknod()) + */ + status = (rtems_status_code)unlink(telnet_ptys[ndx].devname); + if (status != RTEMS_SUCCESSFUL) + perror("removing pty device node from file system"); + else + free(telnet_ptys[ndx].devname); + }; + + free ( telnet_ptys ); + + fprintf(stderr,"PTY driver unloaded successfully\n"); + telnet_pty_inited=FALSE; + return 0; +} + +/* + * Open entry point + */ + +static +rtems_device_driver my_pty_open( + rtems_device_major_number major, + rtems_device_minor_number minor, + void * arg +) +{ + rtems_status_code sc; + sc = rtems_termios_open(major,minor,arg,pty_get_termios_handlers(FALSE)); + return sc; +} + +/* + * Close entry point + */ + +static +rtems_device_driver my_pty_close( + rtems_device_major_number major, + rtems_device_minor_number minor, + void * arg +) +{ + return rtems_termios_close(arg); +} + +/* + * read bytes from the pty + */ + +static +rtems_device_driver my_pty_read( + rtems_device_major_number major, + rtems_device_minor_number minor, + void * arg +) +{ + return rtems_termios_read(arg); +} + +/* + * write bytes to the pty + */ + +static +rtems_device_driver my_pty_write( + rtems_device_major_number major, + rtems_device_minor_number minor, + void * arg +) +{ + return rtems_termios_write(arg); +} + +/* + * IO Control entry point + */ + +static +rtems_device_driver my_pty_control( + rtems_device_major_number major, + rtems_device_minor_number minor, + void * arg +) +{ + rtems_libio_ioctl_args_t *args = (rtems_libio_ioctl_args_t*)arg; + struct winsize *wp = (struct winsize*)args->buffer; + pty_t *p = &telnet_ptys[minor]; + + switch (args->command) { + + case TIOCGWINSZ: + + wp->ws_row = p->height; + wp->ws_col = p->width; + args->ioctl_return=0; +#if DEBUG & DEBUG_WH + fprintf(stderr, + "ioctl(TIOCGWINSZ), returning %ix%i\n", + wp->ws_col, + wp->ws_row); +#endif + + return RTEMS_SUCCESSFUL; + + case TIOCSWINSZ: +#if DEBUG & DEBUG_WH + fprintf(stderr, + "ioctl(TIOCGWINSZ), setting %ix%i\n", + wp->ws_col, + wp->ws_row); +#endif + + p->height = wp->ws_row; + p->width = wp->ws_col; + args->ioctl_return=0; + + return RTEMS_SUCCESSFUL; + + default: + + break; + } + + return rtems_termios_ioctl(arg); +} + +static rtems_driver_address_table drvPty = { + my_pty_initialize, + my_pty_open, + my_pty_close, + my_pty_read, + my_pty_write, + my_pty_control +}; + +/*-----------------------------------------------------------*/ +static const rtems_termios_callbacks pty_poll_callbacks = { + ptyPollInitialize, /* FirstOpen */ + ptyShutdown, /* LastClose */ + ptyPollRead, /* PollRead */ + ptyPollWrite, /* Write */ + ptySetAttributes, /* setAttributes */ + NULL, /* stopRemoteTX */ + NULL, /* StartRemoteTX */ + 0 /* outputUsesInterrupts */ +}; +/*-----------------------------------------------------------*/ +static const rtems_termios_callbacks * pty_get_termios_handlers(int polled) { + return &pty_poll_callbacks; +} +/*-----------------------------------------------------------*/ + +static int pty_do_initialize(void) +{ + if ( !telnet_pty_inited ) { + if (RTEMS_SUCCESSFUL==rtems_io_register_driver(0, &drvPty, &pty_major)) + telnet_pty_inited=TRUE; + else + fprintf(stderr,"WARNING: registering the PTY driver FAILED\n"); + } + return telnet_pty_inited; +} + +int telnet_pty_initialize(void) +{ + return pty_do_initialize(); +} + +int telnet_pty_finalize(void) +{ + return pty_do_finalize(); +} |