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