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