/* * /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 * * - 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 #include #include #include #include #include #include #include #include /*-----------------------------------------*/ #include #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 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;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) { 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 (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 ssize_t ptyPollWrite(int minor, const char * buf, size_t len) { size_t 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(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(); }