git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@40834 a95241bf-73f2-0310-859d-f6bbb57e9c96
616 lines
12 KiB
C++
616 lines
12 KiB
C++
/*
|
|
* Copyright 2007-2010, Haiku, Inc. All rights reserved.
|
|
* Copyright (c) 2003-4 Kian Duffy <[email protected]>
|
|
* Copyright (c) 2004 Daniel Furrer <[email protected]>
|
|
* Parts Copyright (C) 1998,99 Kazuho Okui and Takashi Murai.
|
|
*
|
|
* Distributed under the terms of the MIT license.
|
|
*
|
|
*/
|
|
|
|
|
|
#include "Shell.h"
|
|
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <new>
|
|
#include <pwd.h>
|
|
#include <signal.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stddef.h>
|
|
#include <stdio.h>
|
|
#include <sys/param.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/wait.h>
|
|
#include <termios.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
#include <Catalog.h>
|
|
#include <Entry.h>
|
|
#include <Locale.h>
|
|
#include <OS.h>
|
|
#include <Path.h>
|
|
|
|
#include <util/KMessage.h>
|
|
|
|
#include <extended_system_info.h>
|
|
#include <extended_system_info_defs.h>
|
|
|
|
#include "ActiveProcessInfo.h"
|
|
#include "ShellParameters.h"
|
|
#include "TermConst.h"
|
|
#include "TermParse.h"
|
|
#include "TerminalBuffer.h"
|
|
|
|
|
|
#ifndef CEOF
|
|
#define CEOF ('D'&037)
|
|
#endif
|
|
#ifndef CSUSP
|
|
#define CSUSP ('Z'&037)
|
|
#endif
|
|
#ifndef CQUIT
|
|
#define CQUIT ('\\'&037)
|
|
#endif
|
|
#ifndef CEOL
|
|
#define CEOL 0
|
|
#endif
|
|
#ifndef CSTOP
|
|
#define CSTOP ('Q'&037)
|
|
#endif
|
|
#ifndef CSTART
|
|
#define CSTART ('S'&037)
|
|
#endif
|
|
#ifndef CSWTCH
|
|
#define CSWTCH 0
|
|
#endif
|
|
|
|
const char *kDefaultShell = "/bin/sh";
|
|
|
|
/*
|
|
* Set environment variable.
|
|
*/
|
|
#if defined(HAIKU_TARGET_PLATFORM_BEOS) || \
|
|
defined(HAIKU_TARGET_PLATFORM_BONE) || \
|
|
defined(HAIKU_TARGET_PLATFORM_LIBBE_TEST)
|
|
|
|
extern char **environ;
|
|
|
|
static int setenv(const char *var, const char *value, bool overwrite);
|
|
|
|
static int
|
|
setenv(const char *var, const char *value, bool overwrite)
|
|
{
|
|
int envindex = 0;
|
|
const int len = strlen(var);
|
|
const int val_len = strlen (value);
|
|
|
|
while (environ[envindex] != NULL) {
|
|
if (!strncmp(environ[envindex], var, len)) {
|
|
/* found it */
|
|
if (overwrite) {
|
|
environ[envindex] = (char *)malloc((unsigned)len + val_len + 2);
|
|
sprintf(environ[envindex], "%s=%s", var, value);
|
|
}
|
|
return 0;
|
|
}
|
|
envindex++;
|
|
}
|
|
|
|
environ[envindex] = (char *)malloc((unsigned)len + val_len + 2);
|
|
sprintf(environ[envindex], "%s=%s", var, value);
|
|
environ[++envindex] = NULL;
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
|
|
/* handshake interface */
|
|
typedef struct
|
|
{
|
|
int status; /* status of child */
|
|
char msg[128]; /* error message */
|
|
int row; /* terminal rows */
|
|
int col; /* Terminal columns */
|
|
} handshake_t;
|
|
|
|
/* status of handshake */
|
|
#define PTY_OK 0 /* pty open and set termios OK */
|
|
#define PTY_NG 1 /* pty open or set termios NG */
|
|
#define PTY_WS 2 /* pty need WINSIZE (row and col ) */
|
|
|
|
|
|
Shell::Shell()
|
|
:
|
|
fFd(-1),
|
|
fProcessID(-1),
|
|
fTermParse(NULL),
|
|
fAttached(false)
|
|
{
|
|
}
|
|
|
|
|
|
Shell::~Shell()
|
|
{
|
|
Close();
|
|
}
|
|
|
|
|
|
status_t
|
|
Shell::Open(int row, int col, const ShellParameters& parameters)
|
|
{
|
|
if (fFd >= 0)
|
|
return B_ERROR;
|
|
|
|
status_t status = _Spawn(row, col, parameters);
|
|
if (status < B_OK)
|
|
return status;
|
|
|
|
fTermParse = new (std::nothrow) TermParse(fFd);
|
|
if (fTermParse == NULL) {
|
|
Close();
|
|
return B_NO_MEMORY;
|
|
}
|
|
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
void
|
|
Shell::Close()
|
|
{
|
|
delete fTermParse;
|
|
fTermParse = NULL;
|
|
|
|
if (fFd >= 0) {
|
|
close(fFd);
|
|
kill(-fShellInfo.ProcessID(), SIGHUP);
|
|
fShellInfo.SetProcessID(-1);
|
|
int status;
|
|
wait(&status);
|
|
fFd = -1;
|
|
}
|
|
}
|
|
|
|
|
|
const char *
|
|
Shell::TTYName() const
|
|
{
|
|
return ttyname(fFd);
|
|
}
|
|
|
|
|
|
ssize_t
|
|
Shell::Read(void *buffer, size_t numBytes) const
|
|
{
|
|
if (fFd < 0)
|
|
return B_NO_INIT;
|
|
|
|
return read(fFd, buffer, numBytes);
|
|
}
|
|
|
|
|
|
ssize_t
|
|
Shell::Write(const void *buffer, size_t numBytes)
|
|
{
|
|
if (fFd < 0)
|
|
return B_NO_INIT;
|
|
|
|
return write(fFd, buffer, numBytes);
|
|
}
|
|
|
|
|
|
status_t
|
|
Shell::UpdateWindowSize(int rows, int columns)
|
|
{
|
|
struct winsize winSize;
|
|
winSize.ws_row = rows;
|
|
winSize.ws_col = columns;
|
|
if (ioctl(fFd, TIOCSWINSZ, &winSize) != 0)
|
|
return errno;
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
status_t
|
|
Shell::GetAttr(struct termios &attr) const
|
|
{
|
|
if (tcgetattr(fFd, &attr) < 0)
|
|
return errno;
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
status_t
|
|
Shell::SetAttr(const struct termios &attr)
|
|
{
|
|
if (tcsetattr(fFd, TCSANOW, &attr) < 0)
|
|
return errno;
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
int
|
|
Shell::FD() const
|
|
{
|
|
return fFd;
|
|
}
|
|
|
|
|
|
bool
|
|
Shell::HasActiveProcesses() const
|
|
{
|
|
pid_t running = tcgetpgrp(fFd);
|
|
if (running == fShellInfo.ProcessID() || running == -1)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool
|
|
Shell::GetActiveProcessInfo(ActiveProcessInfo& _info) const
|
|
{
|
|
_info.Unset();
|
|
|
|
// get the foreground process group
|
|
pid_t process = tcgetpgrp(fFd);
|
|
if (process < 0)
|
|
return false;
|
|
|
|
// get more info on the process group leader
|
|
KMessage info;
|
|
status_t error = get_extended_team_info(process, B_TEAM_INFO_BASIC, info);
|
|
if (error != B_OK)
|
|
return false;
|
|
|
|
// fetch the name and the current directory from the info
|
|
const char* name;
|
|
int32 cwdDevice;
|
|
int64 cwdDirectory;
|
|
if (info.FindString("name", &name) != B_OK
|
|
|| info.FindInt32("cwd device", &cwdDevice) != B_OK
|
|
|| info.FindInt64("cwd directory", &cwdDirectory) != B_OK) {
|
|
return false;
|
|
}
|
|
|
|
// convert the node ref into a path
|
|
entry_ref cwdRef(cwdDevice, cwdDirectory, ".");
|
|
BPath cwdPath;
|
|
if (cwdPath.SetTo(&cwdRef) != B_OK)
|
|
return false;
|
|
|
|
// set the result
|
|
_info.SetTo(process, name, cwdPath.Path());
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
status_t
|
|
Shell::AttachBuffer(TerminalBuffer *buffer)
|
|
{
|
|
if (fAttached)
|
|
return B_ERROR;
|
|
|
|
fAttached = true;
|
|
|
|
return fTermParse->StartThreads(buffer);
|
|
}
|
|
|
|
|
|
void
|
|
Shell::DetachBuffer()
|
|
{
|
|
if (fAttached)
|
|
fTermParse->StopThreads();
|
|
}
|
|
|
|
|
|
// private
|
|
static status_t
|
|
send_handshake_message(thread_id target, const handshake_t& handshake)
|
|
{
|
|
return send_data(target, 0, &handshake, sizeof(handshake_t));
|
|
}
|
|
|
|
|
|
static void
|
|
receive_handshake_message(handshake_t& handshake)
|
|
{
|
|
thread_id sender;
|
|
receive_data(&sender, &handshake, sizeof(handshake_t));
|
|
}
|
|
|
|
|
|
static void
|
|
initialize_termios(struct termios &tio)
|
|
{
|
|
/*
|
|
* Set Terminal interface.
|
|
*/
|
|
|
|
tio.c_line = 0;
|
|
tio.c_lflag |= ECHOE;
|
|
|
|
/* input: nl->nl, cr->nl */
|
|
tio.c_iflag &= ~(INLCR|IGNCR);
|
|
tio.c_iflag |= ICRNL;
|
|
tio.c_iflag &= ~ISTRIP;
|
|
|
|
/* output: cr->cr, nl in not retrun, no delays, ln->cr/ln */
|
|
tio.c_oflag &= ~(OCRNL|ONLRET|NLDLY|CRDLY|TABDLY|BSDLY|VTDLY|FFDLY);
|
|
tio.c_oflag |= ONLCR;
|
|
tio.c_oflag |= OPOST;
|
|
|
|
/* baud rate is 19200 (equal beterm) */
|
|
tio.c_cflag &= ~(CBAUD);
|
|
tio.c_cflag |= B19200;
|
|
|
|
tio.c_cflag &= ~CSIZE;
|
|
tio.c_cflag |= CS8;
|
|
tio.c_cflag |= CREAD;
|
|
|
|
tio.c_cflag |= HUPCL;
|
|
tio.c_iflag &= ~(IGNBRK|BRKINT);
|
|
|
|
/*
|
|
* enable signals, canonical processing (erase, kill, etc), echo.
|
|
*/
|
|
tio.c_lflag |= ISIG|ICANON|ECHO|ECHOE|ECHONL;
|
|
tio.c_lflag &= ~(ECHOK | IEXTEN);
|
|
|
|
/* set control characters. */
|
|
tio.c_cc[VINTR] = 'C' & 0x1f; /* '^C' */
|
|
tio.c_cc[VQUIT] = CQUIT; /* '^\' */
|
|
tio.c_cc[VERASE] = 0x7f; /* '^?' */
|
|
tio.c_cc[VKILL] = 'U' & 0x1f; /* '^U' */
|
|
tio.c_cc[VEOF] = CEOF; /* '^D' */
|
|
tio.c_cc[VEOL] = CEOL; /* '^@' */
|
|
tio.c_cc[VMIN] = 4;
|
|
tio.c_cc[VTIME] = 0;
|
|
tio.c_cc[VEOL2] = CEOL; /* '^@' */
|
|
tio.c_cc[VSWTCH] = CSWTCH; /* '^@' */
|
|
tio.c_cc[VSTART] = CSTART; /* '^S' */
|
|
tio.c_cc[VSTOP] = CSTOP; /* '^Q' */
|
|
tio.c_cc[VSUSP] = CSUSP; /* '^Z' */
|
|
}
|
|
|
|
#undef B_TRANSLATE_CONTEXT
|
|
#define B_TRANSLATE_CONTEXT "Terminal Shell"
|
|
|
|
status_t
|
|
Shell::_Spawn(int row, int col, const ShellParameters& parameters)
|
|
{
|
|
const char** argv = (const char**)parameters.Arguments();
|
|
int argc = parameters.ArgumentCount();
|
|
const char* defaultArgs[3] = {kDefaultShell, "-l", NULL};
|
|
struct passwd passwdStruct;
|
|
struct passwd *passwdResult;
|
|
char stringBuffer[256];
|
|
|
|
if (argv == NULL || argc == 0) {
|
|
if (!getpwuid_r(getuid(), &passwdStruct, stringBuffer,
|
|
sizeof(stringBuffer), &passwdResult)) {
|
|
defaultArgs[0] = passwdStruct.pw_shell;
|
|
}
|
|
|
|
argv = defaultArgs;
|
|
argc = 2;
|
|
|
|
fShellInfo.SetDefaultShell(true);
|
|
} else
|
|
fShellInfo.SetDefaultShell(false);
|
|
|
|
signal(SIGTTOU, SIG_IGN);
|
|
|
|
// get a pseudo-tty
|
|
int master = posix_openpt(O_RDWR | O_NOCTTY);
|
|
const char *ttyName;
|
|
|
|
if (master < 0) {
|
|
fprintf(stderr, "Didn't find any available pseudo ttys.");
|
|
return errno;
|
|
}
|
|
|
|
if (grantpt(master) != 0 || unlockpt(master) != 0
|
|
|| (ttyName = ptsname(master)) == NULL) {
|
|
close(master);
|
|
fprintf(stderr, "Failed to init pseudo tty.");
|
|
return errno;
|
|
}
|
|
|
|
/*
|
|
* Get the modes of the current terminal. We will duplicates these
|
|
* on the pseudo terminal.
|
|
*/
|
|
|
|
thread_id terminalThread = find_thread(NULL);
|
|
|
|
/* Fork a child process. */
|
|
fShellInfo.SetProcessID(fork());
|
|
if (fShellInfo.ProcessID() < 0) {
|
|
close(master);
|
|
return B_ERROR;
|
|
}
|
|
|
|
handshake_t handshake;
|
|
|
|
if (fShellInfo.ProcessID() == 0) {
|
|
// Now in child process.
|
|
|
|
// close the PTY master side
|
|
close(master);
|
|
|
|
/*
|
|
* Make our controlling tty the pseudo tty. This hapens because
|
|
* we cleared our original controlling terminal above.
|
|
*/
|
|
|
|
/* Set process session leader */
|
|
if (setsid() < 0) {
|
|
handshake.status = PTY_NG;
|
|
snprintf(handshake.msg, sizeof(handshake.msg),
|
|
"could not set session leader.");
|
|
send_handshake_message(terminalThread, handshake);
|
|
exit(1);
|
|
}
|
|
|
|
/* open slave pty */
|
|
int slave = -1;
|
|
if ((slave = open(ttyName, O_RDWR)) < 0) {
|
|
handshake.status = PTY_NG;
|
|
snprintf(handshake.msg, sizeof(handshake.msg),
|
|
"can't open tty (%s).", ttyName);
|
|
send_handshake_message(terminalThread, handshake);
|
|
exit(1);
|
|
}
|
|
|
|
/* set signal default */
|
|
signal(SIGCHLD, SIG_DFL);
|
|
signal(SIGHUP, SIG_DFL);
|
|
signal(SIGQUIT, SIG_DFL);
|
|
signal(SIGTERM, SIG_DFL);
|
|
signal(SIGINT, SIG_DFL);
|
|
signal(SIGTTOU, SIG_DFL);
|
|
|
|
struct termios tio;
|
|
/* get tty termios (not necessary).
|
|
* TODO: so why are we doing it ?
|
|
*/
|
|
tcgetattr(slave, &tio);
|
|
|
|
initialize_termios(tio);
|
|
|
|
/*
|
|
* change control tty.
|
|
*/
|
|
|
|
dup2(slave, 0);
|
|
dup2(slave, 1);
|
|
dup2(slave, 2);
|
|
|
|
/* close old slave fd. */
|
|
if (slave > 2)
|
|
close(slave);
|
|
|
|
/*
|
|
* set terminal interface.
|
|
*/
|
|
if (tcsetattr(0, TCSANOW, &tio) == -1) {
|
|
handshake.status = PTY_NG;
|
|
snprintf(handshake.msg, sizeof(handshake.msg),
|
|
"failed set terminal interface (TERMIOS).");
|
|
send_handshake_message(terminalThread, handshake);
|
|
exit(1);
|
|
}
|
|
|
|
/*
|
|
* set window size.
|
|
*/
|
|
|
|
handshake.status = PTY_WS;
|
|
send_handshake_message(terminalThread, handshake);
|
|
receive_handshake_message(handshake);
|
|
|
|
if (handshake.status != PTY_WS) {
|
|
handshake.status = PTY_NG;
|
|
snprintf(handshake.msg, sizeof(handshake.msg),
|
|
"mismatch handshake.");
|
|
send_handshake_message(terminalThread, handshake);
|
|
exit(1);
|
|
}
|
|
|
|
struct winsize ws = { handshake.row, handshake.col };
|
|
|
|
ioctl(0, TIOCSWINSZ, &ws);
|
|
|
|
tcsetpgrp(0, getpgrp());
|
|
// set this process group ID as the controlling terminal
|
|
set_thread_priority(find_thread(NULL), B_NORMAL_PRIORITY);
|
|
|
|
/* pty open and set termios successful. */
|
|
handshake.status = PTY_OK;
|
|
send_handshake_message(terminalThread, handshake);
|
|
|
|
/*
|
|
* setenv TERM and TTY.
|
|
*/
|
|
setenv("TERM", "xterm", true);
|
|
setenv("TTY", ttyName, true);
|
|
setenv("TTYPE", parameters.Encoding(), true);
|
|
|
|
// set the current working directory, if one is given
|
|
if (parameters.CurrentDirectory().Length() > 0)
|
|
chdir(parameters.CurrentDirectory().String());
|
|
|
|
execve(argv[0], (char * const *)argv, environ);
|
|
|
|
// Exec failed.
|
|
// TODO: This doesn't belong here.
|
|
|
|
sleep(1);
|
|
|
|
BString alertCommand = "alert --stop '";
|
|
alertCommand += B_TRANSLATE("Cannot execute \"%command\":\n\t%error");
|
|
alertCommand += "' '";
|
|
alertCommand += B_TRANSLATE("Use default shell");
|
|
alertCommand += "' '";
|
|
alertCommand += B_TRANSLATE("Abort");
|
|
alertCommand += "'";
|
|
alertCommand.ReplaceFirst("%command", argv[0]);
|
|
alertCommand.ReplaceFirst("%error", strerror(errno));
|
|
|
|
int returnValue = system(alertCommand.String());
|
|
if (returnValue == 0) {
|
|
execl(kDefaultShell, kDefaultShell,
|
|
"-l", NULL);
|
|
}
|
|
|
|
exit(1);
|
|
}
|
|
|
|
/*
|
|
* In parent Process, Set up the input and output file pointers so
|
|
* that they can write and read the pseudo terminal.
|
|
*/
|
|
|
|
/*
|
|
* close parent control tty.
|
|
*/
|
|
|
|
int done = 0;
|
|
while (!done) {
|
|
receive_handshake_message(handshake);
|
|
|
|
switch (handshake.status) {
|
|
case PTY_OK:
|
|
done = 1;
|
|
break;
|
|
|
|
case PTY_NG:
|
|
fprintf(stderr, "%s\n", handshake.msg);
|
|
done = -1;
|
|
break;
|
|
|
|
case PTY_WS:
|
|
handshake.row = row;
|
|
handshake.col = col;
|
|
handshake.status = PTY_WS;
|
|
send_handshake_message(fShellInfo.ProcessID(), handshake);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (done <= 0)
|
|
return B_ERROR;
|
|
|
|
fFd = master;
|
|
|
|
return B_OK;
|
|
}
|
|
|