/** * @file * * @brief /dev/ptyXX (Support for pseudo-terminals) */ /* * Copyright (c) 2001 Fernando Ruiz Casas * * 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 /*-----------------------------------------*/ #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 RTEMS_PTY_SB_MAX static bool ptyPollInitialize(rtems_termios_tty *, rtems_termios_device_context *, struct termios *, rtems_libio_open_close_args_t *); static void ptyShutdown(rtems_termios_tty *, rtems_termios_device_context *, rtems_libio_open_close_args_t *); static void ptyPollWrite(rtems_termios_device_context *, const char *, size_t); static int ptyPollRead(rtems_termios_device_context *); static bool ptySetAttributes(rtems_termios_device_context *, const struct termios *); static int my_pty_control(rtems_termios_device_context *, ioctl_command_t, void *); static const rtems_termios_device_handler pty_handler = { .first_open = ptyPollInitialize, .last_close = ptyShutdown, .poll_read = ptyPollRead, .write = ptyPollWrite, .set_attributes = ptySetAttributes, .ioctl = my_pty_control }; static int send_iac(rtems_pty_context *pty, unsigned char mode, unsigned char option) { unsigned char buf[3]; buf[0]=IAC_ESC; buf[1]=mode; buf[2]=option; return write(pty->socket, buf, sizeof(buf)); } const char *rtems_pty_initialize(rtems_pty_context *pty, uintptr_t unique) { rtems_status_code sc; memset(pty, 0, sizeof(*pty)); (void)snprintf(pty->name, sizeof(pty->name), "/dev/pty%" PRIuPTR, unique); rtems_termios_device_context_initialize(&pty->base, "pty"); pty->socket = -1; sc = rtems_termios_device_install(pty->name, &pty_handler, NULL, &pty->base); if (sc != RTEMS_SUCCESSFUL) { return NULL; } return pty->name; } void rtems_pty_close_socket(rtems_pty_context *pty) { if (pty->socket >= 0) { (void)close(pty->socket); pty->socket = -1; } } void rtems_pty_set_socket(rtems_pty_context *pty, int socket) { struct timeval t; rtems_pty_close_socket(pty); pty->socket = socket; /* set a long polling interval to save CPU time */ t.tv_sec=2; t.tv_usec=00000; (void)setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, &t, sizeof(t)); /* inform the client that we will echo */ send_iac(pty, IAC_WILL, 1); } /*-----------------------------------------------------------*/ /* * 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(rtems_pty_context *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 ptyPollRead(rtems_termios_device_context *base) { /* Characters written to the client side*/ rtems_pty_context *pty = (rtems_pty_context *)base; unsigned char value; unsigned int omod; int count; int result; 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(pty,IAC_DONT, 34); /*LINEMODE*/ send_iac(pty,IAC_DO , 1); /*ECHO */ } else if (value==31) { send_iac(pty,IAC_DO , 31); /*NAWS */ #if DEBUG & DEBUG_DETAIL printk("replied DO NAWS\n"); #endif } else { send_iac(pty,IAC_DONT,value); } return -1; case IAC_DONT: return -1; case IAC_DO : if (value==3) { send_iac(pty,IAC_WILL, 3); /* GO AHEAD*/ } else if (value==1) { send_iac(pty,IAC_WILL, 1); /* ECHO */ } else { send_iac(pty,IAC_WONT,value); }; return -1; case IAC_WONT: if (value==1) { send_iac(pty,IAC_WILL, 1); } else { /* ECHO */ send_iac(pty,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; } /*-----------------------------------------------------------*/ /* Set the 'Hardware' */ /*-----------------------------------------------------------*/ static bool ptySetAttributes(rtems_termios_device_context *base, const struct termios *t) { rtems_pty_context *pty = (rtems_pty_context *)base; pty->c_cflag = t->c_cflag; return true; } /*-----------------------------------------------------------*/ static bool ptyPollInitialize(rtems_termios_tty *ttyp, rtems_termios_device_context *base, struct termios *t, rtems_libio_open_close_args_t *args) { rtems_pty_context *pty = (rtems_pty_context *)base; pty->ttyp = ttyp; pty->iac_mode = 0; pty->sb_ind = 0; pty->width = 0; pty->height = 0; return ptySetAttributes(&pty->base, t); } /*-----------------------------------------------------------*/ static void ptyShutdown(rtems_termios_tty *ttyp, rtems_termios_device_context *base, rtems_libio_open_close_args_t *arg) { rtems_pty_context *pty = (rtems_pty_context *)base; close(pty->socket); } /*-----------------------------------------------------------*/ /* Write Characters into pty device */ /*-----------------------------------------------------------*/ static void ptyPollWrite(rtems_termios_device_context *base, const char *buf, size_t len) { rtems_pty_context *pty = (rtems_pty_context *)base; while (len > 0) { ssize_t n = write(pty->socket, buf, len); if (n <= 0) { break; } buf += (size_t)n; len -= (size_t)n; } } static int my_pty_control(rtems_termios_device_context *base, ioctl_command_t request, void *buffer) { rtems_pty_context *p = (rtems_pty_context *)base; struct winsize *wp = buffer; switch (request) { case TIOCGWINSZ: wp->ws_row = p->height; wp->ws_col = p->width; #if DEBUG & DEBUG_WH fprintf(stderr, "ioctl(TIOCGWINSZ), returning %ix%i\n", wp->ws_col, wp->ws_row); #endif break; 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; break; default: rtems_set_errno_and_return_minus_one(EINVAL); break; } return 0; }