vfs: check userland buffers before reading them.
[haiku.git] / src / apps / terminal / Shell.cpp
blobbb26169ae5c9f3523acb7e1b7be1e9fbc76dea22
1 /*
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.
9 * Authors:
10 * Kian Duffy, myob@users.sourceforge.net
11 * Daniel Furrer, assimil8or@users.sourceforge.net
12 * Siarzhuk Zharski, zharik@gmx.li
16 #include "Shell.h"
18 #include <dirent.h>
19 #include <errno.h>
20 #include <fcntl.h>
21 #include <new>
22 #include <pwd.h>
23 #include <signal.h>
24 #include <string.h>
25 #include <stdlib.h>
26 #include <stddef.h>
27 #include <stdio.h>
28 #include <sys/param.h>
29 #include <sys/stat.h>
30 #include <sys/wait.h>
31 #include <termios.h>
32 #include <time.h>
33 #include <unistd.h>
35 #include <Catalog.h>
36 #include <Entry.h>
37 #include <Locale.h>
38 #include <OS.h>
39 #include <Path.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"
53 #ifndef CEOF
54 #define CEOF ('D'&037)
55 #endif
56 #ifndef CSUSP
57 #define CSUSP ('Z'&037)
58 #endif
59 #ifndef CQUIT
60 #define CQUIT ('\\'&037)
61 #endif
62 #ifndef CEOL
63 #define CEOL 0
64 #endif
65 #ifndef CSTOP
66 #define CSTOP ('Q'&037)
67 #endif
68 #ifndef CSTART
69 #define CSTART ('S'&037)
70 #endif
71 #ifndef CSWTCH
72 #define CSWTCH 0
73 #endif
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);
90 static int
91 setenv(const char *var, const char *value, bool overwrite)
93 int envindex = 0;
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)) {
99 /* found it */
100 if (overwrite) {
101 environ[envindex] = (char *)malloc((unsigned)len + val_len + 2);
102 sprintf(environ[envindex], "%s=%s", var, value);
104 return 0;
106 envindex++;
109 environ[envindex] = (char *)malloc((unsigned)len + val_len + 2);
110 sprintf(environ[envindex], "%s=%s", var, value);
111 environ[++envindex] = NULL;
112 return 0;
114 #endif
117 /* handshake interface */
118 typedef struct
120 int status; /* status of child */
121 char msg[128]; /* error message */
122 unsigned short row; /* terminal rows */
123 unsigned short col; /* Terminal columns */
124 } handshake_t;
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 ) */
132 Shell::Shell()
134 fFd(-1),
135 fProcessID(-1),
136 fTermParse(NULL),
137 fAttached(false)
142 Shell::~Shell()
144 Close();
148 status_t
149 Shell::Open(int row, int col, const ShellParameters& parameters)
151 if (fFd >= 0)
152 return B_ERROR;
154 status_t status = _Spawn(row, col, parameters);
155 if (status < B_OK)
156 return status;
158 fTermParse = new (std::nothrow) TermParse(fFd);
159 if (fTermParse == NULL) {
160 Close();
161 return B_NO_MEMORY;
164 return B_OK;
168 void
169 Shell::Close()
171 delete fTermParse;
172 fTermParse = NULL;
174 if (fFd >= 0) {
175 close(fFd);
176 kill(-fShellInfo.ProcessID(), SIGHUP);
177 fShellInfo.SetProcessID(-1);
178 int status;
179 wait(&status);
180 fFd = -1;
185 const char *
186 Shell::TTYName() const
188 return ttyname(fFd);
192 ssize_t
193 Shell::Read(void *buffer, size_t numBytes) const
195 if (fFd < 0)
196 return B_NO_INIT;
198 return read(fFd, buffer, numBytes);
202 ssize_t
203 Shell::Write(const void *buffer, size_t numBytes)
205 if (fFd < 0)
206 return B_NO_INIT;
208 return write(fFd, buffer, numBytes);
212 status_t
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)
219 return errno;
220 return B_OK;
224 status_t
225 Shell::GetAttr(struct termios &attr) const
227 if (tcgetattr(fFd, &attr) < 0)
228 return errno;
229 return B_OK;
233 status_t
234 Shell::SetAttr(const struct termios &attr)
236 if (tcsetattr(fFd, TCSANOW, &attr) < 0)
237 return errno;
238 return B_OK;
243 Shell::FD() const
245 return fFd;
249 bool
250 Shell::HasActiveProcesses() const
252 pid_t running = tcgetpgrp(fFd);
253 if (running == fShellInfo.ProcessID() || running == -1)
254 return false;
256 return true;
260 bool
261 Shell::GetActiveProcessInfo(ActiveProcessInfo& _info) const
263 _info.Unset();
265 // get the foreground process group
266 pid_t process = tcgetpgrp(fFd);
267 if (process < 0)
268 return false;
270 // get more info on the process group leader
271 KMessage info;
272 status_t error = get_extended_team_info(process, B_TEAM_INFO_BASIC, info);
273 if (error != B_OK)
274 return false;
276 // fetch the name and the current directory from the info
277 const char* name;
278 int32 cwdDevice;
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) {
283 return false;
286 // convert the node ref into a path
287 entry_ref cwdRef(cwdDevice, cwdDirectory, ".");
288 BPath cwdPath;
289 if (cwdPath.SetTo(&cwdRef) != B_OK)
290 return false;
292 // set the result
293 _info.SetTo(process, name, cwdPath.Path());
295 return true;
299 status_t
300 Shell::AttachBuffer(TerminalBuffer *buffer)
302 if (fAttached)
303 return B_ERROR;
305 fAttached = true;
307 return fTermParse->StartThreads(buffer);
311 void
312 Shell::DetachBuffer()
314 if (fAttached)
315 fTermParse->StopThreads();
319 // private
320 static status_t
321 send_handshake_message(thread_id target, const handshake_t& handshake)
323 return send_data(target, 0, &handshake, sizeof(handshake_t));
327 static void
328 receive_handshake_message(handshake_t& handshake)
330 thread_id sender;
331 receive_data(&sender, &handshake, sizeof(handshake_t));
335 static void
336 initialize_termios(struct termios &tio)
339 * Set Terminal interface.
342 tio.c_line = 0;
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;
360 tio.c_cflag |= CS8;
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; /* '^@' */
379 tio.c_cc[VMIN] = 4;
380 tio.c_cc[VTIME] = 0;
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"
392 status_t
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;
409 argv = defaultArgs;
410 argc = 2;
412 fShellInfo.SetDefaultShell(true);
413 } else
414 fShellInfo.SetDefaultShell(false);
416 fShellInfo.SetEncoding(parameters.Encoding());
418 signal(SIGTTOU, SIG_IGN);
420 // get a pseudo-tty
421 int master = posix_openpt(O_RDWR | O_NOCTTY);
422 const char *ttyName;
424 if (master < 0) {
425 fprintf(stderr, "Didn't find any available pseudo ttys.");
426 return errno;
429 if (grantpt(master) != 0 || unlockpt(master) != 0
430 || (ttyName = ptsname(master)) == NULL) {
431 close(master);
432 fprintf(stderr, "Failed to init pseudo tty.");
433 return errno;
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) {
446 close(master);
447 return B_ERROR;
450 handshake_t handshake;
452 if (fShellInfo.ProcessID() == 0) {
453 // Now in child process.
455 // close the PTY master side
456 close(master);
459 * Make our controlling tty the pseudo tty. This hapens because
460 * we cleared our original controlling terminal above.
463 /* Set process session leader */
464 if (setsid() < 0) {
465 handshake.status = PTY_NG;
466 snprintf(handshake.msg, sizeof(handshake.msg),
467 "could not set session leader.");
468 send_handshake_message(terminalThread, handshake);
469 exit(1);
472 /* open slave pty */
473 int slave = -1;
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);
479 exit(1);
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);
490 struct termios tio;
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.
502 dup2(slave, 0);
503 dup2(slave, 1);
504 dup2(slave, 2);
506 /* close old slave fd. */
507 if (slave > 2)
508 close(slave);
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);
518 exit(1);
522 * set window size.
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);
534 exit(1);
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);
562 // Exec failed.
563 // TODO: This doesn't belong here.
565 sleep(1);
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");
573 alertCommand += "'";
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,
580 "-l", NULL);
583 exit(1);
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.
595 int done = 0;
596 while (!done) {
597 receive_handshake_message(handshake);
599 switch (handshake.status) {
600 case PTY_OK:
601 done = 1;
602 break;
604 case PTY_NG:
605 fprintf(stderr, "%s\n", handshake.msg);
606 done = -1;
607 break;
609 case PTY_WS:
610 handshake.row = row;
611 handshake.col = col;
612 handshake.status = PTY_WS;
613 send_handshake_message(fShellInfo.ProcessID(), handshake);
614 break;
618 if (done <= 0) {
619 close(master);
620 return B_ERROR;
623 fFd = master;
625 return B_OK;