mirror of
https://git.rtems.org/rtems-libbsd/
synced 2025-05-14 05:29:43 +08:00
655 lines
17 KiB
C
655 lines
17 KiB
C
/*
|
|
* /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();
|
|
}
|