xref: /haiku/src/apps/terminal/Shell.cpp (revision f9443b14d5faa43f98a0a7a5cc85994599bc11a3)
1 /*
2  * Copyright 2007-2010, 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.
6  *
7  * Distributed under the terms of the MIT license.
8  *
9  */
10 
11 
12 #include "Shell.h"
13 
14 #include <dirent.h>
15 #include <errno.h>
16 #include <fcntl.h>
17 #include <new>
18 #include <pwd.h>
19 #include <signal.h>
20 #include <string.h>
21 #include <stdlib.h>
22 #include <stddef.h>
23 #include <stdio.h>
24 #include <sys/param.h>
25 #include <sys/stat.h>
26 #include <sys/wait.h>
27 #include <termios.h>
28 #include <time.h>
29 #include <unistd.h>
30 
31 #include <Catalog.h>
32 #include <Entry.h>
33 #include <Locale.h>
34 #include <OS.h>
35 #include <Path.h>
36 
37 #include <util/KMessage.h>
38 
39 #include <extended_system_info.h>
40 #include <extended_system_info_defs.h>
41 
42 #include "ActiveProcessInfo.h"
43 #include "ShellParameters.h"
44 #include "TermConst.h"
45 #include "TermParse.h"
46 #include "TerminalBuffer.h"
47 
48 
49 #ifndef CEOF
50 #define CEOF ('D'&037)
51 #endif
52 #ifndef CSUSP
53 #define CSUSP ('Z'&037)
54 #endif
55 #ifndef CQUIT
56 #define CQUIT ('\\'&037)
57 #endif
58 #ifndef CEOL
59 #define CEOL 0
60 #endif
61 #ifndef CSTOP
62 #define CSTOP ('Q'&037)
63 #endif
64 #ifndef CSTART
65 #define CSTART ('S'&037)
66 #endif
67 #ifndef CSWTCH
68 #define CSWTCH 0
69 #endif
70 
71 const char *kDefaultShell = "/bin/sh";
72 
73 /*
74  * Set environment variable.
75  */
76 #if defined(HAIKU_TARGET_PLATFORM_BEOS) || \
77 	defined(HAIKU_TARGET_PLATFORM_BONE) || \
78 	defined(HAIKU_TARGET_PLATFORM_LIBBE_TEST)
79 
80 extern char **environ;
81 
82 static int setenv(const char *var, const char *value, bool overwrite);
83 
84 static int
85 setenv(const char *var, const char *value, bool overwrite)
86 {
87 	int envindex = 0;
88 	const int len = strlen(var);
89 	const int val_len = strlen (value);
90 
91 	while (environ[envindex] != NULL) {
92 		if (!strncmp(environ[envindex], var, len)) {
93 			/* found it */
94 			if (overwrite) {
95 				environ[envindex] = (char *)malloc((unsigned)len + val_len + 2);
96 				sprintf(environ[envindex], "%s=%s", var, value);
97 			}
98 			return 0;
99 		}
100 		envindex++;
101 	}
102 
103 	environ[envindex] = (char *)malloc((unsigned)len + val_len + 2);
104 	sprintf(environ[envindex], "%s=%s", var, value);
105 	environ[++envindex] = NULL;
106 	return 0;
107 }
108 #endif
109 
110 
111 /* handshake interface */
112 typedef struct
113 {
114 	int status;		/* status of child */
115 	char msg[128];	/* error message */
116 	int row;		/* terminal rows */
117 	int col;		/* Terminal columns */
118 } handshake_t;
119 
120 /* status of handshake */
121 #define PTY_OK	0	/* pty open and set termios OK */
122 #define PTY_NG	1	/* pty open or set termios NG */
123 #define PTY_WS	2	/* pty need WINSIZE (row and col ) */
124 
125 
126 Shell::Shell()
127 	:
128 	fFd(-1),
129 	fProcessID(-1),
130 	fTermParse(NULL),
131 	fAttached(false)
132 {
133 }
134 
135 
136 Shell::~Shell()
137 {
138 	Close();
139 }
140 
141 
142 status_t
143 Shell::Open(int row, int col, const ShellParameters& parameters)
144 {
145 	if (fFd >= 0)
146 		return B_ERROR;
147 
148 	status_t status = _Spawn(row, col, parameters);
149 	if (status < B_OK)
150 		return status;
151 
152 	fTermParse = new (std::nothrow) TermParse(fFd);
153 	if (fTermParse == NULL) {
154 		Close();
155 		return B_NO_MEMORY;
156 	}
157 
158 	return B_OK;
159 }
160 
161 
162 void
163 Shell::Close()
164 {
165 	delete fTermParse;
166 	fTermParse = NULL;
167 
168 	if (fFd >= 0) {
169 		close(fFd);
170 		kill(-fShellInfo.ProcessID(), SIGHUP);
171 		fShellInfo.SetProcessID(-1);
172 		int status;
173 		wait(&status);
174 		fFd = -1;
175 	}
176 }
177 
178 
179 const char *
180 Shell::TTYName() const
181 {
182 	return ttyname(fFd);
183 }
184 
185 
186 ssize_t
187 Shell::Read(void *buffer, size_t numBytes) const
188 {
189 	if (fFd < 0)
190 		return B_NO_INIT;
191 
192 	return read(fFd, buffer, numBytes);
193 }
194 
195 
196 ssize_t
197 Shell::Write(const void *buffer, size_t numBytes)
198 {
199 	if (fFd < 0)
200 		return B_NO_INIT;
201 
202 	return write(fFd, buffer, numBytes);
203 }
204 
205 
206 status_t
207 Shell::UpdateWindowSize(int rows, int columns)
208 {
209 	struct winsize winSize;
210 	winSize.ws_row = rows;
211 	winSize.ws_col = columns;
212 	if (ioctl(fFd, TIOCSWINSZ, &winSize) != 0)
213 		return errno;
214 	return B_OK;
215 }
216 
217 
218 status_t
219 Shell::GetAttr(struct termios &attr) const
220 {
221 	if (tcgetattr(fFd, &attr) < 0)
222 		return errno;
223 	return B_OK;
224 }
225 
226 
227 status_t
228 Shell::SetAttr(const struct termios &attr)
229 {
230 	if (tcsetattr(fFd, TCSANOW, &attr) < 0)
231 		return errno;
232 	return B_OK;
233 }
234 
235 
236 int
237 Shell::FD() const
238 {
239 	return fFd;
240 }
241 
242 
243 bool
244 Shell::HasActiveProcesses() const
245 {
246 	pid_t running = tcgetpgrp(fFd);
247 	if (running == fShellInfo.ProcessID() || running == -1)
248 		return false;
249 
250 	return true;
251 }
252 
253 
254 bool
255 Shell::GetActiveProcessInfo(ActiveProcessInfo& _info) const
256 {
257 	_info.Unset();
258 
259 	// get the foreground process group
260 	pid_t process = tcgetpgrp(fFd);
261 	if (process < 0)
262 		return false;
263 
264 	// get more info on the process group leader
265 	KMessage info;
266 	status_t error = get_extended_team_info(process, B_TEAM_INFO_BASIC, info);
267 	if (error != B_OK)
268 		return false;
269 
270 	// fetch the name and the current directory from the info
271 	const char* name;
272 	int32 cwdDevice;
273 	int64 cwdDirectory;
274 	if (info.FindString("name", &name) != B_OK
275 		|| info.FindInt32("cwd device", &cwdDevice) != B_OK
276 		|| info.FindInt64("cwd directory", &cwdDirectory) != B_OK) {
277 		return false;
278 	}
279 
280 	// convert the node ref into a path
281 	entry_ref cwdRef(cwdDevice, cwdDirectory, ".");
282 	BPath cwdPath;
283 	if (cwdPath.SetTo(&cwdRef) != B_OK)
284 		return false;
285 
286 	// set the result
287 	_info.SetTo(process, name, cwdPath.Path());
288 
289 	return true;
290 }
291 
292 
293 status_t
294 Shell::AttachBuffer(TerminalBuffer *buffer)
295 {
296 	if (fAttached)
297 		return B_ERROR;
298 
299 	fAttached = true;
300 
301 	return fTermParse->StartThreads(buffer);
302 }
303 
304 
305 void
306 Shell::DetachBuffer()
307 {
308 	if (fAttached)
309 		fTermParse->StopThreads();
310 }
311 
312 
313 // private
314 static status_t
315 send_handshake_message(thread_id target, const handshake_t& handshake)
316 {
317 	return send_data(target, 0, &handshake, sizeof(handshake_t));
318 }
319 
320 
321 static void
322 receive_handshake_message(handshake_t& handshake)
323 {
324 	thread_id sender;
325 	receive_data(&sender, &handshake, sizeof(handshake_t));
326 }
327 
328 
329 static void
330 initialize_termios(struct termios &tio)
331 {
332 	/*
333 	 * Set Terminal interface.
334 	 */
335 
336 	tio.c_line = 0;
337 	tio.c_lflag |= ECHOE;
338 
339 	/* input: nl->nl, cr->nl */
340 	tio.c_iflag &= ~(INLCR|IGNCR);
341 	tio.c_iflag |= ICRNL;
342 	tio.c_iflag &= ~ISTRIP;
343 
344 	/* output: cr->cr, nl in not retrun, no delays, ln->cr/ln */
345 	tio.c_oflag &= ~(OCRNL|ONLRET|NLDLY|CRDLY|TABDLY|BSDLY|VTDLY|FFDLY);
346 	tio.c_oflag |= ONLCR;
347 	tio.c_oflag |= OPOST;
348 
349 	/* baud rate is 19200 (equal beterm) */
350 	tio.c_cflag &= ~(CBAUD);
351 	tio.c_cflag |= B19200;
352 
353 	tio.c_cflag &= ~CSIZE;
354 	tio.c_cflag |= CS8;
355 	tio.c_cflag |= CREAD;
356 
357 	tio.c_cflag |= HUPCL;
358 	tio.c_iflag &= ~(IGNBRK|BRKINT);
359 
360 	/*
361 	 * enable signals, canonical processing (erase, kill, etc), echo.
362 	*/
363 	tio.c_lflag |= ISIG|ICANON|ECHO|ECHOE|ECHONL;
364 	tio.c_lflag &= ~(ECHOK | IEXTEN);
365 
366 	/* set control characters. */
367 	tio.c_cc[VINTR]  = 'C' & 0x1f;	/* '^C'	*/
368 	tio.c_cc[VQUIT]  = CQUIT;		/* '^\'	*/
369 	tio.c_cc[VERASE] = 0x7f;		/* '^?'	*/
370 	tio.c_cc[VKILL]  = 'U' & 0x1f;	/* '^U'	*/
371 	tio.c_cc[VEOF]   = CEOF;		/* '^D' */
372 	tio.c_cc[VEOL]   = CEOL;		/* '^@' */
373 	tio.c_cc[VMIN]   = 4;
374 	tio.c_cc[VTIME]  = 0;
375 	tio.c_cc[VEOL2]  = CEOL;		/* '^@' */
376 	tio.c_cc[VSWTCH] = CSWTCH;		/* '^@' */
377 	tio.c_cc[VSTART] = CSTART;		/* '^S' */
378 	tio.c_cc[VSTOP]  = CSTOP;		/* '^Q' */
379 	tio.c_cc[VSUSP]  = CSUSP;		/* '^Z' */
380 }
381 
382 #undef B_TRANSLATION_CONTEXT
383 #define B_TRANSLATION_CONTEXT "Terminal Shell"
384 
385 status_t
386 Shell::_Spawn(int row, int col, const ShellParameters& parameters)
387 {
388 	const char** argv = (const char**)parameters.Arguments();
389 	int argc = parameters.ArgumentCount();
390 	const char* defaultArgs[3] = {kDefaultShell, "-l", NULL};
391 	struct passwd passwdStruct;
392 	struct passwd *passwdResult;
393 	char stringBuffer[256];
394 
395 	if (argv == NULL || argc == 0) {
396 		if (!getpwuid_r(getuid(), &passwdStruct, stringBuffer,
397 				sizeof(stringBuffer), &passwdResult)) {
398 			defaultArgs[0] = passwdStruct.pw_shell;
399 		}
400 
401 		argv = defaultArgs;
402 		argc = 2;
403 
404 		fShellInfo.SetDefaultShell(true);
405 	} else
406 		fShellInfo.SetDefaultShell(false);
407 
408 	signal(SIGTTOU, SIG_IGN);
409 
410 	// get a pseudo-tty
411 	int master = posix_openpt(O_RDWR | O_NOCTTY);
412 	const char *ttyName;
413 
414 	if (master < 0) {
415 		fprintf(stderr, "Didn't find any available pseudo ttys.");
416 		return errno;
417 	}
418 
419 	if (grantpt(master) != 0 || unlockpt(master) != 0
420 		|| (ttyName = ptsname(master)) == NULL) {
421 		close(master);
422 		fprintf(stderr, "Failed to init pseudo tty.");
423 		return errno;
424 	}
425 
426 	/*
427 	 * Get the modes of the current terminal. We will duplicates these
428 	 * on the pseudo terminal.
429 	 */
430 
431 	thread_id terminalThread = find_thread(NULL);
432 
433 	/* Fork a child process. */
434 	fShellInfo.SetProcessID(fork());
435 	if (fShellInfo.ProcessID() < 0) {
436 		close(master);
437 		return B_ERROR;
438 	}
439 
440 	handshake_t handshake;
441 
442 	if (fShellInfo.ProcessID() == 0) {
443 		// Now in child process.
444 
445 		// close the PTY master side
446 		close(master);
447 
448 		/*
449 		 * Make our controlling tty the pseudo tty. This hapens because
450 		 * we cleared our original controlling terminal above.
451 		 */
452 
453 		/* Set process session leader */
454 		if (setsid() < 0) {
455 			handshake.status = PTY_NG;
456 			snprintf(handshake.msg, sizeof(handshake.msg),
457 				"could not set session leader.");
458 			send_handshake_message(terminalThread, handshake);
459 			exit(1);
460 		}
461 
462 		/* open slave pty */
463 		int slave = -1;
464 		if ((slave = open(ttyName, O_RDWR)) < 0) {
465 			handshake.status = PTY_NG;
466 			snprintf(handshake.msg, sizeof(handshake.msg),
467 				"can't open tty (%s).", ttyName);
468 			send_handshake_message(terminalThread, handshake);
469 			exit(1);
470 		}
471 
472 		/* set signal default */
473 		signal(SIGCHLD, SIG_DFL);
474 		signal(SIGHUP, SIG_DFL);
475 		signal(SIGQUIT, SIG_DFL);
476 		signal(SIGTERM, SIG_DFL);
477 		signal(SIGINT, SIG_DFL);
478 		signal(SIGTTOU, SIG_DFL);
479 
480 		struct termios tio;
481 		/* get tty termios (not necessary).
482 		 * TODO: so why are we doing it ?
483 		 */
484 		tcgetattr(slave, &tio);
485 
486 		initialize_termios(tio);
487 
488 		/*
489 		 * change control tty.
490 		 */
491 
492 		dup2(slave, 0);
493 		dup2(slave, 1);
494 		dup2(slave, 2);
495 
496 		/* close old slave fd. */
497 		if (slave > 2)
498 			close(slave);
499 
500 		/*
501 		 * set terminal interface.
502 		 */
503 		if (tcsetattr(0, TCSANOW, &tio) == -1) {
504 			handshake.status = PTY_NG;
505 			snprintf(handshake.msg, sizeof(handshake.msg),
506 				"failed set terminal interface (TERMIOS).");
507 			send_handshake_message(terminalThread, handshake);
508 			exit(1);
509 		}
510 
511 		/*
512 		 * set window size.
513 		 */
514 
515 		handshake.status = PTY_WS;
516 		send_handshake_message(terminalThread, handshake);
517 		receive_handshake_message(handshake);
518 
519 		if (handshake.status != PTY_WS) {
520 			handshake.status = PTY_NG;
521 			snprintf(handshake.msg, sizeof(handshake.msg),
522 				"mismatch handshake.");
523 			send_handshake_message(terminalThread, handshake);
524 			exit(1);
525 		}
526 
527 		struct winsize ws = { handshake.row, handshake.col };
528 
529 		ioctl(0, TIOCSWINSZ, &ws);
530 
531 		tcsetpgrp(0, getpgrp());
532 			// set this process group ID as the controlling terminal
533 		set_thread_priority(find_thread(NULL), B_NORMAL_PRIORITY);
534 
535 		/* pty open and set termios successful. */
536 		handshake.status = PTY_OK;
537 		send_handshake_message(terminalThread, handshake);
538 
539 		/*
540 		 * setenv TERM and TTY.
541 		 */
542 		setenv("TERM", "xterm", true);
543 		setenv("TTY", ttyName, true);
544 		setenv("TTYPE", parameters.Encoding(), true);
545 
546 		// set the current working directory, if one is given
547 		if (parameters.CurrentDirectory().Length() > 0)
548 			chdir(parameters.CurrentDirectory().String());
549 
550 		execve(argv[0], (char * const *)argv, environ);
551 
552 		// Exec failed.
553 		// TODO: This doesn't belong here.
554 
555 		sleep(1);
556 
557 		BString alertCommand = "alert --stop '";
558 		alertCommand += B_TRANSLATE("Cannot execute \"%command\":\n\t%error");
559 		alertCommand += "' '";
560 		alertCommand += B_TRANSLATE("Use default shell");
561 		alertCommand += "' '";
562 		alertCommand += B_TRANSLATE("Abort");
563 		alertCommand += "'";
564 		alertCommand.ReplaceFirst("%command", argv[0]);
565 		alertCommand.ReplaceFirst("%error", strerror(errno));
566 
567 		int returnValue = system(alertCommand.String());
568 		if (returnValue == 0) {
569 			execl(kDefaultShell, kDefaultShell,
570 				"-l", NULL);
571 		}
572 
573 		exit(1);
574 	}
575 
576 	/*
577 	 * In parent Process, Set up the input and output file pointers so
578 	 * that they can write and read the pseudo terminal.
579 	 */
580 
581 	/*
582 	 * close parent control tty.
583 	 */
584 
585 	int done = 0;
586 	while (!done) {
587 		receive_handshake_message(handshake);
588 
589 		switch (handshake.status) {
590 			case PTY_OK:
591 				done = 1;
592 				break;
593 
594 			case PTY_NG:
595 				fprintf(stderr, "%s\n", handshake.msg);
596 				done = -1;
597 				break;
598 
599 			case PTY_WS:
600 				handshake.row = row;
601 				handshake.col = col;
602 				handshake.status = PTY_WS;
603 				send_handshake_message(fShellInfo.ProcessID(), handshake);
604 				break;
605 		}
606 	}
607 
608 	if (done <= 0)
609 		return B_ERROR;
610 
611 	fFd = master;
612 
613 	return B_OK;
614 }
615 
616