You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

254 lines
7.0 KiB

/* -------------------------------------------------------------------------- */
/* */
/* [shell.cpp] Shell */
/* */
/* -------------------------------------------------------------------------- */
/* */
/* Copyright (c) 1997,1998 by Lars Doelle <lars.doelle@on-line.de> */
/* */
/* This file is part of Konsole - an X terminal for KDE */
/* -------------------------------------------------------------------------- */
/*! /class Shell (FIXME: rename to TEpty or so)
Shells provide a pseudo terminal connection to a program. Although it is
closely related to a pipe, these pseudo terminal connections have some
ability, that makes it nessesary to uses them. Most importent, they know
about changing screen sizes (/sa setSize).
Pseudo terminals are a unique feature of UNIX, and always come in form of
pairs of devices (/dev/ptyXX and /dev/ttyXX), which are connected to each
other by the operating system. One may think of them as two serial devices
linked by a null-modem cable. Being based on devices the number of
simultanous instances of this class is (globally) limited by the number of
those device pairs, which is 256.
The pty is for the Shell while the program gets the tty.
There's a sinister ioctl(2), signal(2) and job control stuff
nessesary to make everything work as it should. (/sa makeShell)
*/
#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <grp.h>
#include "../../config.h"
#if defined (_HPUX_SOURCE)
#define _TERMIOS_INCLUDED
#include <bsdtty.h>
#endif
#include <assert.h>
#include <time.h>
#include <signal.h>
#include <qintdict.h>
#include <sys/wait.h>
#include "TEShell.h"
#include "TEShell.moc"
#include <qstring.h>
#define HERE fprintf(stdout,"%s(%d): here\n",__FILE__,__LINE__)
FILE* syslog = NULL; //stdout;
/* -------------------------------------------------------------------------- */
void Shell::setSize(int lines, int columns)
// Tell the teletype handler what size the window is.
// Called after a window size change.
{ struct winsize wsize;
if(fd < 0) return;
wsize.ws_row = (unsigned short)lines;
wsize.ws_col = (unsigned short)columns;
ioctl(fd,TIOCSWINSZ,(char *)&wsize);
}
static char ptynam[] = "/dev/ptyxx";
static char ttynam[] = "/dev/ttyxx";
static QIntDict<Shell> shells;
static void catchChild(int)
// Catch a SIGCHLD signal and propagate that the child died.
{ int status;
pid_t pid = wait(&status);
Shell* sh = shells.find(pid);
if (sh) { shells.remove(pid); sh->doneShell(status); }
}
void Shell::doneShell(int status)
{
emit done(status);
}
int Shell::run(QStrList & args, const char* term)
{
pid_t comm_pid = fork();
if (comm_pid < 0) { fprintf(stderr,"Can't fork\n"); return -1; }
if (comm_pid == 0) makeShell(ttynam,args,term);
if (comm_pid > 0) shells.insert(comm_pid,this);
return 0;
}
void Shell::makeShell(const char* dev, QStrList & args,
const char* term)
// only used internally. See `run' for interface
{ int sig; char* t;
// open and set all standard files to master/slave tty
int tt = open(dev, O_RDWR | O_EXCL);
//reset signal handlers for child process
for (sig = 1; sig < NSIG; sig++) signal(sig,SIG_DFL);
// Don't know why, but his is vital for SIGHUP to find the child.
// Could be, we get rid of the controling terminal by this.
for (int i = 0; i < getdtablesize(); i++) if (i != tt && i != fd) close(i);
dup2(tt,fileno(stdin));
dup2(tt,fileno(stdout));
dup2(tt,fileno(stderr));
if (tt > 2) close(tt);
// Setup job control
// "There be dragons."
// (Ancient world map)
if (setsid() < 0) perror("failed to set process group"); // (vital for bash)
#if defined(TIOCSCTTY)
ioctl(0, TIOCSCTTY, 0);
#endif
int pgrp = getpid(); // This sequence is necessary for
ioctl(0, TIOCSPGRP, (char *)&pgrp); // event propagation. Omitting this
setpgid(0,0); // is not noticeable with all
close(open(dev, O_WRONLY, 0)); // clients (bash,vi). Because bash
setpgid(0,0); // heals this, use '-e' to test it.
/*
#if defined(TIOCSPTLCK)
int flag = 1; // Linux-only security solution:
if (ioctl(fd,TIOCSPTLCK,&flag)) // prohibit opening tty from now on
#endif
perror("Warning: The session is insecure.");
*/
close(fd);
// drop privileges
setuid(getuid()); setgid(getgid());
// propagate emulation
if (term && term[0]) setenv("TERM",term,1);
// convert QStrList into char*[]
unsigned int i;
char **argv = (char**)malloc(sizeof(char*)*(args.count()+1));
for (i = 0; i<args.count(); i++) argv[i] = strdup(args.at(i));
argv[i] = 0L;
// setup for login shells
char *f = argv[0];
if ( login_shell ) // see sh(1)
{
t = strrchr( argv[0], '/' );
t = strdup(t);
*t = '-';
argv[0] = t;
}
// finally, pass to the new program
execvp(f, argv);
perror("exec failed");
exit(1); // control should never come here.
}
int openShell()
{ int ptyfd; char *s3, *s4;
static char ptyc3[] = "pqrstuvwxyzabcde";
static char ptyc4[] = "0123456789abcdef";
// Find a master pty that we can open ////////////////////////////////
ptyfd = -1;
for (s3 = ptyc3; *s3 != 0; s3++)
{
for (s4 = ptyc4; *s4 != 0; s4++)
{
ptynam[8] = ttynam[8] = *s3;
ptynam[9] = ttynam[9] = *s4;
if ((ptyfd = open(ptynam,O_RDWR)) >= 0)
{
if (geteuid() == 0 || access(ttynam,R_OK|W_OK) == 0) break;
close(ptyfd); ptyfd = -1;
}
}
if (ptyfd >= 0) break;
}
if (ptyfd < 0) { fprintf(stderr,"Can't open a pseudo teletype\n"); exit(1); }
fcntl(ptyfd,F_SETFL,O_NDELAY);
return ptyfd;
}
Shell::Shell(int ls)
/* setup shell */
{
login_shell=ls;
fd = openShell();
signal(SIGCHLD,catchChild);
mn = new QSocketNotifier(fd, QSocketNotifier::Read);
mw = new QSocketNotifier(fd, QSocketNotifier::Write);
connect( mn, SIGNAL(activated(int)), this, SLOT(DataReceived(int)) );
connect( mw, SIGNAL(activated(int)), this, SLOT(DataWritten(int)) );
}
Shell::~Shell()
{
delete mn;
delete mw;
close(fd);
}
void Shell::send_byte(char c)
{
write(fd,&c,1); mw->setEnabled(TRUE);
}
void Shell::send_string(const char* s)
{
write(fd,s,strlen(s)); mw->setEnabled(TRUE);
}
void Shell::send_bytes(const char* s, int len)
{
write(fd,s,len); mw->setEnabled(TRUE);
}
void Shell::DataReceived(int)
{ char buf[4096];
int n = read(fd, buf, 4096);
emit block_in(buf,n);
if (syslog) // if (debugging) ...
{
int i;
for (i = 0; i < n; i++) fputc(buf[i],syslog);
fflush(syslog);
}
}
void Shell::DataWritten(int)
{
mw->setEnabled(FALSE); written();
}