/* * /dev/ptyXX (A first version for pseudo-terminals) * * 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 * * - 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. * * $Id$ */ /* LICENSE INFORMATION RTEMS is free software; you can redistribute it and/or modify it under terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. RTEMS 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. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with RTEMS; see file COPYING. If not, write to the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. As a special exception, including RTEMS header files in a file, instantiating RTEMS generics or templates, or linking other files with RTEMS objects to produce an executable application, does not by itself cause the resulting executable application to be covered by the GNU General Public License. This exception does not however invalidate any other reasons why the executable file might be covered by the GNU Public License. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #define DEBUG_WH (1<<0) #define DEBUG_DETAIL (1<<1) /* #define DEBUG DEBUG_WH */ #ifdef __cplusplus extern "C" { #endif /*-----------------------------------------*/ #include #include #include #include #include #include #include #if 0 #include #else #define MAX_PTYS 8 #endif #include #include #ifdef __cplusplus }; #endif /*-----------------------------------------*/ #include #include #include #include /*-----------------------------------------*/ #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 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; }; #ifdef __cplusplus extern "C" { int printk(char*,...); #endif #if MAX_PTYS > 5 #undef MAX_PTYS #define MAX_PTYS 5 #endif static int telnet_pty_inited=FALSE; static pty_t telnet_ptys[MAX_PTYS]; static rtems_device_major_number pty_major; /* 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) { for (ndx=0;ndxsb_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; isb_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) { /* 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 #if 0 /* pass CRLF through - they should use termios to handle it */ || ((value=='\n') && (pty->last_cr)) #endif /* but 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 int ptyPollWrite(int minor, const char * buf,int 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 (minorc_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 (minoriop->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=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 int ptyPollWrite(int minor, const char * buf,int len) { int count; if (minorbuffer; 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() { 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; } #ifdef __cplusplus class TelnetPtyIni { public: TelnetPtyIni() { if (!nest++) { pty_do_initialize(); } }; ~TelnetPtyIni(){ if (!--nest) { pty_do_finalize(); } }; private: static int nest; }; static TelnetPtyIni onlyInst; int TelnetPtyIni::nest=0; int telnet_pty_initialize() { return telnet_pty_inited; } int telnet_pty_finalize() { return telnet_pty_inited; } }; #else int telnet_pty_initialize() { return pty_do_initialize(); } int telnet_pty_finalize() { return pty_do_finalize(); } #endif