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