2 * Copyright 2007-2013, Haiku, Inc. All rights reserved.
3 * Copyright (c) 2003-4 Kian Duffy <myob@users.sourceforge.net>
4 * Copyright (c) 2004 Daniel Furrer <assimil8or@users.sourceforge.net>
5 * Parts Copyright (C) 1998,99 Kazuho Okui and Takashi Murai.
7 * Distributed under the terms of the MIT license.
10 * Kian Duffy, myob@users.sourceforge.net
11 * Daniel Furrer, assimil8or@users.sourceforge.net
12 * Siarzhuk Zharski, zharik@gmx.li
28 #include <sys/param.h>
41 #include <util/KMessage.h>
43 #include <extended_system_info.h>
44 #include <extended_system_info_defs.h>
46 #include "ActiveProcessInfo.h"
47 #include "ShellParameters.h"
48 #include "TermConst.h"
49 #include "TermParse.h"
50 #include "TerminalBuffer.h"
54 #define CEOF ('D'&037)
57 #define CSUSP ('Z'&037)
60 #define CQUIT ('\\'&037)
66 #define CSTOP ('Q'&037)
69 #define CSTART ('S'&037)
75 // TODO: should extract from /etc/passwd instead???
76 const char *kDefaultShell
= "/bin/sh";
77 const char *kTerminalType
= "xterm-256color";
80 * Set environment variable.
82 #if defined(HAIKU_TARGET_PLATFORM_BEOS) || \
83 defined(HAIKU_TARGET_PLATFORM_BONE) || \
84 defined(HAIKU_TARGET_PLATFORM_LIBBE_TEST)
86 extern char **environ
;
88 static int setenv(const char *var
, const char *value
, bool overwrite
);
91 setenv(const char *var
, const char *value
, bool overwrite
)
94 const int len
= strlen(var
);
95 const int val_len
= strlen (value
);
97 while (environ
[envindex
] != NULL
) {
98 if (!strncmp(environ
[envindex
], var
, len
)) {
101 environ
[envindex
] = (char *)malloc((unsigned)len
+ val_len
+ 2);
102 sprintf(environ
[envindex
], "%s=%s", var
, value
);
109 environ
[envindex
] = (char *)malloc((unsigned)len
+ val_len
+ 2);
110 sprintf(environ
[envindex
], "%s=%s", var
, value
);
111 environ
[++envindex
] = NULL
;
117 /* handshake interface */
120 int status
; /* status of child */
121 char msg
[128]; /* error message */
122 unsigned short row
; /* terminal rows */
123 unsigned short col
; /* Terminal columns */
126 /* status of handshake */
127 #define PTY_OK 0 /* pty open and set termios OK */
128 #define PTY_NG 1 /* pty open or set termios NG */
129 #define PTY_WS 2 /* pty need WINSIZE (row and col ) */
149 Shell::Open(int row
, int col
, const ShellParameters
& parameters
)
154 status_t status
= _Spawn(row
, col
, parameters
);
158 fTermParse
= new (std::nothrow
) TermParse(fFd
);
159 if (fTermParse
== NULL
) {
176 kill(-fShellInfo
.ProcessID(), SIGHUP
);
177 fShellInfo
.SetProcessID(-1);
186 Shell::TTYName() const
193 Shell::Read(void *buffer
, size_t numBytes
) const
198 return read(fFd
, buffer
, numBytes
);
203 Shell::Write(const void *buffer
, size_t numBytes
)
208 return write(fFd
, buffer
, numBytes
);
213 Shell::UpdateWindowSize(int rows
, int columns
)
215 struct winsize winSize
;
216 winSize
.ws_row
= rows
;
217 winSize
.ws_col
= columns
;
218 if (ioctl(fFd
, TIOCSWINSZ
, &winSize
) != 0)
225 Shell::GetAttr(struct termios
&attr
) const
227 if (tcgetattr(fFd
, &attr
) < 0)
234 Shell::SetAttr(const struct termios
&attr
)
236 if (tcsetattr(fFd
, TCSANOW
, &attr
) < 0)
250 Shell::HasActiveProcesses() const
252 pid_t running
= tcgetpgrp(fFd
);
253 if (running
== fShellInfo
.ProcessID() || running
== -1)
261 Shell::GetActiveProcessInfo(ActiveProcessInfo
& _info
) const
265 // get the foreground process group
266 pid_t process
= tcgetpgrp(fFd
);
270 // get more info on the process group leader
272 status_t error
= get_extended_team_info(process
, B_TEAM_INFO_BASIC
, info
);
276 // fetch the name and the current directory from the info
279 int64 cwdDirectory
= 0;
280 if (info
.FindString("name", &name
) != B_OK
281 || info
.FindInt32("cwd device", &cwdDevice
) != B_OK
282 || info
.FindInt64("cwd directory", &cwdDirectory
) != B_OK
) {
286 // convert the node ref into a path
287 entry_ref
cwdRef(cwdDevice
, cwdDirectory
, ".");
289 if (cwdPath
.SetTo(&cwdRef
) != B_OK
)
293 _info
.SetTo(process
, name
, cwdPath
.Path());
300 Shell::AttachBuffer(TerminalBuffer
*buffer
)
307 return fTermParse
->StartThreads(buffer
);
312 Shell::DetachBuffer()
315 fTermParse
->StopThreads();
321 send_handshake_message(thread_id target
, const handshake_t
& handshake
)
323 return send_data(target
, 0, &handshake
, sizeof(handshake_t
));
328 receive_handshake_message(handshake_t
& handshake
)
331 receive_data(&sender
, &handshake
, sizeof(handshake_t
));
336 initialize_termios(struct termios
&tio
)
339 * Set Terminal interface.
343 tio
.c_lflag
|= ECHOE
;
345 /* input: nl->nl, cr->nl */
346 tio
.c_iflag
&= ~(INLCR
|IGNCR
);
347 tio
.c_iflag
|= ICRNL
;
348 tio
.c_iflag
&= ~ISTRIP
;
350 /* output: cr->cr, nl in not retrun, no delays, ln->cr/ln */
351 tio
.c_oflag
&= ~(OCRNL
|ONLRET
|NLDLY
|CRDLY
|TABDLY
|BSDLY
|VTDLY
|FFDLY
);
352 tio
.c_oflag
|= ONLCR
;
353 tio
.c_oflag
|= OPOST
;
355 /* baud rate is 19200 (equal beterm) */
356 tio
.c_cflag
&= ~(CBAUD
);
357 tio
.c_cflag
|= B19200
;
359 tio
.c_cflag
&= ~CSIZE
;
361 tio
.c_cflag
|= CREAD
;
363 tio
.c_cflag
|= HUPCL
;
364 tio
.c_iflag
&= ~(IGNBRK
|BRKINT
);
367 * enable signals, canonical processing (erase, kill, etc), echo.
369 tio
.c_lflag
|= ISIG
|ICANON
|ECHO
|ECHOE
|ECHONL
;
370 tio
.c_lflag
&= ~(ECHOK
| IEXTEN
);
372 /* set control characters. */
373 tio
.c_cc
[VINTR
] = 'C' & 0x1f; /* '^C' */
374 tio
.c_cc
[VQUIT
] = CQUIT
; /* '^\' */
375 tio
.c_cc
[VERASE
] = 0x7f; /* '^?' */
376 tio
.c_cc
[VKILL
] = 'U' & 0x1f; /* '^U' */
377 tio
.c_cc
[VEOF
] = CEOF
; /* '^D' */
378 tio
.c_cc
[VEOL
] = CEOL
; /* '^@' */
381 tio
.c_cc
[VEOL2
] = CEOL
; /* '^@' */
382 tio
.c_cc
[VSWTCH
] = CSWTCH
; /* '^@' */
383 tio
.c_cc
[VSTART
] = CSTART
; /* '^S' */
384 tio
.c_cc
[VSTOP
] = CSTOP
; /* '^Q' */
385 tio
.c_cc
[VSUSP
] = CSUSP
; /* '^Z' */
389 #undef B_TRANSLATION_CONTEXT
390 #define B_TRANSLATION_CONTEXT "Terminal Shell"
393 Shell::_Spawn(int row
, int col
, const ShellParameters
& parameters
)
395 const char** argv
= (const char**)parameters
.Arguments();
396 int argc
= parameters
.ArgumentCount();
397 const char* defaultArgs
[3] = {kDefaultShell
, "-l", NULL
};
398 struct passwd passwdStruct
;
399 struct passwd
*passwdResult
;
400 char stringBuffer
[256];
402 if (argv
== NULL
|| argc
== 0) {
403 if (getpwuid_r(getuid(), &passwdStruct
, stringBuffer
,
404 sizeof(stringBuffer
), &passwdResult
) == 0
405 && passwdResult
!= NULL
) {
406 defaultArgs
[0] = passwdStruct
.pw_shell
;
412 fShellInfo
.SetDefaultShell(true);
414 fShellInfo
.SetDefaultShell(false);
416 fShellInfo
.SetEncoding(parameters
.Encoding());
418 signal(SIGTTOU
, SIG_IGN
);
421 int master
= posix_openpt(O_RDWR
| O_NOCTTY
);
425 fprintf(stderr
, "Didn't find any available pseudo ttys.");
429 if (grantpt(master
) != 0 || unlockpt(master
) != 0
430 || (ttyName
= ptsname(master
)) == NULL
) {
432 fprintf(stderr
, "Failed to init pseudo tty.");
437 * Get the modes of the current terminal. We will duplicates these
438 * on the pseudo terminal.
441 thread_id terminalThread
= find_thread(NULL
);
443 /* Fork a child process. */
444 fShellInfo
.SetProcessID(fork());
445 if (fShellInfo
.ProcessID() < 0) {
450 handshake_t handshake
;
452 if (fShellInfo
.ProcessID() == 0) {
453 // Now in child process.
455 // close the PTY master side
459 * Make our controlling tty the pseudo tty. This hapens because
460 * we cleared our original controlling terminal above.
463 /* Set process session leader */
465 handshake
.status
= PTY_NG
;
466 snprintf(handshake
.msg
, sizeof(handshake
.msg
),
467 "could not set session leader.");
468 send_handshake_message(terminalThread
, handshake
);
474 if ((slave
= open(ttyName
, O_RDWR
)) < 0) {
475 handshake
.status
= PTY_NG
;
476 snprintf(handshake
.msg
, sizeof(handshake
.msg
),
477 "can't open tty (%s).", ttyName
);
478 send_handshake_message(terminalThread
, handshake
);
482 /* set signal default */
483 signal(SIGCHLD
, SIG_DFL
);
484 signal(SIGHUP
, SIG_DFL
);
485 signal(SIGQUIT
, SIG_DFL
);
486 signal(SIGTERM
, SIG_DFL
);
487 signal(SIGINT
, SIG_DFL
);
488 signal(SIGTTOU
, SIG_DFL
);
491 /* get tty termios (not necessary).
492 * TODO: so why are we doing it ?
494 tcgetattr(slave
, &tio
);
496 initialize_termios(tio
);
499 * change control tty.
506 /* close old slave fd. */
511 * set terminal interface.
513 if (tcsetattr(0, TCSANOW
, &tio
) == -1) {
514 handshake
.status
= PTY_NG
;
515 snprintf(handshake
.msg
, sizeof(handshake
.msg
),
516 "failed set terminal interface (TERMIOS).");
517 send_handshake_message(terminalThread
, handshake
);
525 handshake
.status
= PTY_WS
;
526 send_handshake_message(terminalThread
, handshake
);
527 receive_handshake_message(handshake
);
529 if (handshake
.status
!= PTY_WS
) {
530 handshake
.status
= PTY_NG
;
531 snprintf(handshake
.msg
, sizeof(handshake
.msg
),
532 "mismatch handshake.");
533 send_handshake_message(terminalThread
, handshake
);
537 struct winsize ws
= { handshake
.row
, handshake
.col
};
539 ioctl(0, TIOCSWINSZ
, &ws
);
541 tcsetpgrp(0, getpgrp());
542 // set this process group ID as the controlling terminal
543 set_thread_priority(find_thread(NULL
), B_NORMAL_PRIORITY
);
545 /* pty open and set termios successful. */
546 handshake
.status
= PTY_OK
;
547 send_handshake_message(terminalThread
, handshake
);
550 * setenv TERM and TTY.
552 setenv("TERM", kTerminalType
, true);
553 setenv("TTY", ttyName
, true);
554 setenv("TTYPE", fShellInfo
.EncodingName(), true);
556 // set the current working directory, if one is given
557 if (parameters
.CurrentDirectory().Length() > 0)
558 chdir(parameters
.CurrentDirectory().String());
560 execve(argv
[0], (char * const *)argv
, environ
);
563 // TODO: This doesn't belong here.
567 BString alertCommand
= "alert --stop '";
568 alertCommand
+= B_TRANSLATE("Cannot execute \"%command\":\n\t%error");
569 alertCommand
+= "' '";
570 alertCommand
+= B_TRANSLATE("Use default shell");
571 alertCommand
+= "' '";
572 alertCommand
+= B_TRANSLATE("Abort");
574 alertCommand
.ReplaceFirst("%command", argv
[0]);
575 alertCommand
.ReplaceFirst("%error", strerror(errno
));
577 int returnValue
= system(alertCommand
.String());
578 if (returnValue
== 0) {
579 execl(kDefaultShell
, kDefaultShell
,
587 * In parent Process, Set up the input and output file pointers so
588 * that they can write and read the pseudo terminal.
592 * close parent control tty.
597 receive_handshake_message(handshake
);
599 switch (handshake
.status
) {
605 fprintf(stderr
, "%s\n", handshake
.msg
);
612 handshake
.status
= PTY_WS
;
613 send_handshake_message(fShellInfo
.ProcessID(), handshake
);