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