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