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