xref: /haiku/src/apps/terminal/Shell.cpp (revision 1214ef1b2100f2b3299fc9d8d6142e46f70a4c3f)
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 /* default shell command and options. */
59 const char *kDefaultShellCommand = { "/bin/sh" "-login" };
60 
61 
62 /*
63  * Set environment variable.
64  */
65 #if defined(HAIKU_TARGET_PLATFORM_BEOS) || defined(HAIKU_TARGET_PLATFORM_LIBBE_TEST)
66 
67 extern char **environ;
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 
152 	if (fFd >= 0) {
153 		close(fFd);
154 		kill(-fProcessID, SIGHUP);
155 		fProcessID = -1;
156 		int status;
157 		wait(&status);
158 		fFd = -1;
159 	}
160 }
161 
162 
163 const char *
164 Shell::TTYName() const
165 {
166 	return ttyname(fFd);
167 }
168 
169 
170 ssize_t
171 Shell::Read(void *buffer, size_t numBytes)
172 {
173 	if (fFd < 0)
174 		return B_NO_INIT;
175 
176 	return read(fFd, buffer, numBytes);
177 }
178 
179 
180 ssize_t
181 Shell::Write(const void *buffer, size_t numBytes)
182 {
183 	if (fFd < 0)
184 		return B_NO_INIT;
185 
186 	return write(fFd, buffer, numBytes);
187 }
188 
189 
190 status_t
191 Shell::UpdateWindowSize(int rows, int columns)
192 {
193 	struct winsize winSize;
194 	winSize.ws_row = rows;
195 	winSize.ws_col = columns;
196 	ioctl(fFd, TIOCSWINSZ, &winSize);
197 	return Signal(SIGWINCH);
198 }
199 
200 
201 status_t
202 Shell::Signal(int signal)
203 {
204 	return send_signal(-fProcessID, signal);
205 }
206 
207 
208 status_t
209 Shell::GetAttr(struct termios &attr)
210 {
211 	if (tcgetattr(fFd, &attr) < 0)
212 		return errno;
213 	return B_OK;
214 }
215 
216 
217 status_t
218 Shell::SetAttr(struct termios &attr)
219 {
220 	if (tcsetattr(fFd, TCSANOW, &attr) < 0)
221 		return errno;
222 	return B_OK;
223 }
224 
225 
226 int
227 Shell::FD() const
228 {
229 	return fFd;
230 }
231 
232 
233 void
234 Shell::ViewAttached(TermView *view)
235 {
236 	if (fAttached)
237 		return;
238 
239 	status_t status = fTermParse->StartThreads(view);
240 	if (status < B_OK) {
241 		// TODO: What can we do here ?
242 		fprintf(stderr, "Shell:ViewAttached():"
243 				" cannot start parser threads: %s",
244 				strerror(status));
245 	}
246 }
247 
248 
249 void
250 Shell::ViewDetached()
251 {
252 	if (fAttached)
253 		fTermParse->StopThreads();
254 }
255 
256 
257 // private
258 static status_t
259 send_handshake_message(thread_id target, const handshake_t& handshake)
260 {
261 	return send_data(target, 0, &handshake, sizeof(handshake_t));
262 }
263 
264 
265 static void
266 receive_handshake_message(handshake_t& handshake)
267 {
268 	thread_id sender;
269 	receive_data(&sender, &handshake, sizeof(handshake_t));
270 }
271 
272 
273 status_t
274 Shell::_Spawn(int row, int col, const char *encoding, int argc, const char **argv)
275 {
276 	signal(SIGTTOU, SIG_IGN);
277 
278 	/*
279 	 * Get a pseudo-tty. We do this by cycling through files in the
280 	 * directory. The operationg system will not allow us to open a master
281 	 * which is already in use, so we simply go until the open succeeds.
282 	 */
283 	char ttyName[B_PATH_NAME_LENGTH];
284 	int master = -1;
285 	DIR *dir = opendir("/dev/pt/");
286 	if (dir != NULL) {
287 		struct dirent *dirEntry;
288 		while ((dirEntry = readdir(dir)) != NULL) {
289 			// skip '.' and '..'
290 			if (dirEntry->d_name[0] == '.')
291 				continue;
292 
293 			char ptyName[B_PATH_NAME_LENGTH];
294 			snprintf(ptyName, sizeof(ptyName), "/dev/pt/%s", dirEntry->d_name);
295 
296 			master = open(ptyName, O_RDWR);
297 			if (master >= 0) {
298 				// Set the tty that corresponds to the pty we found
299 				snprintf(ttyName, sizeof(ttyName), "/dev/tt/%s", dirEntry->d_name);
300 				break;
301 			} else {
302 				// B_BUSY is a normal case
303 				if (errno != B_BUSY)
304 					fprintf(stderr, "could not open %s: %s\n", ptyName, strerror(errno));
305 			}
306 		}
307 		closedir(dir);
308 	}
309 
310 	if (master < 0) {
311     		printf("didn't find any available pseudo ttys.");
312     		return B_ERROR;
313 	}
314 
315        	/*
316 	 * Get the modes of the current terminal. We will duplicates these
317 	 * on the pseudo terminal.
318 	 */
319 
320 	thread_id terminalThread = find_thread(NULL);
321 
322 	/* Fork a child process. */
323 	if ((fProcessID = fork()) < 0) {
324 		close(master);
325 		return B_ERROR;
326 	}
327 
328 	handshake_t handshake;
329 
330 	if (fProcessID == 0) {
331 		// Now in child process.
332 
333 		/*
334 		 * Make our controlling tty the pseudo tty. This hapens because
335 		 * we cleared our original controlling terminal above.
336 		 */
337 
338 		/* Set process session leader */
339 		if (setsid() < 0) {
340 			handshake.status = PTY_NG;
341 			snprintf(handshake.msg, sizeof(handshake.msg),
342 				"could not set session leader.");
343 			send_handshake_message(terminalThread, handshake);
344 			exit(1);
345 		}
346 
347 		/* change pty owner and access mode. */
348 		chown(ttyName, getuid(), getgid());
349 		chmod(ttyName, S_IRUSR | S_IWUSR);
350 
351 		/* open slave pty */
352 		int slave = -1;
353 		if ((slave = open(ttyName, O_RDWR)) < 0) {
354 			handshake.status = PTY_NG;
355 			snprintf(handshake.msg, sizeof(handshake.msg),
356 				"can't open tty (%s).", ttyName);
357 			send_handshake_message(terminalThread, handshake);
358 			exit(1);
359 		}
360 
361 		/* set signal default */
362 		signal(SIGCHLD, SIG_DFL);
363 		signal(SIGHUP, SIG_DFL);
364 		signal(SIGQUIT, SIG_DFL);
365 		signal(SIGTERM, SIG_DFL);
366 		signal(SIGINT, SIG_DFL);
367 		signal(SIGTTOU, SIG_DFL);
368 
369 		struct termios tio;
370 		/* get tty termios (not necessary).
371 		 * TODO: so why are we doing it ?
372 		 */
373 		tcgetattr(slave, &tio);
374 
375 		/*
376 		 * Set Terminal interface.
377 		 */
378 
379 		tio.c_line = 0;
380 		tio.c_lflag |= ECHOE;
381 
382 		/* input: nl->nl, cr->nl */
383 		tio.c_iflag &= ~(INLCR|IGNCR);
384 		tio.c_iflag |= ICRNL;
385 		tio.c_iflag &= ~ISTRIP;
386 
387 		/* output: cr->cr, nl in not retrun, no delays, ln->cr/ln */
388 		tio.c_oflag &= ~(OCRNL|ONLRET|NLDLY|CRDLY|TABDLY|BSDLY|VTDLY|FFDLY);
389 		tio.c_oflag |= ONLCR;
390 		tio.c_oflag |= OPOST;
391 
392 		/* baud rate is 19200 (equal beterm) */
393 		tio.c_cflag &= ~(CBAUD);
394 		tio.c_cflag |= B19200;
395 
396 		tio.c_cflag &= ~CSIZE;
397 		tio.c_cflag |= CS8;
398 		tio.c_cflag |= CREAD;
399 
400 		tio.c_cflag |= HUPCL;
401 		tio.c_iflag &= ~(IGNBRK|BRKINT);
402 
403 		/*
404 		 * enable signals, canonical processing (erase, kill, etc), echo.
405 		*/
406 		tio.c_lflag |= ISIG|ICANON|ECHO|ECHOE|ECHONL;
407 		tio.c_lflag &= ~(ECHOK | IEXTEN);
408 
409 		/* set control characters. */
410 		tio.c_cc[VINTR]  = 'C' & 0x1f;	/* '^C'	*/
411 		tio.c_cc[VQUIT]  = CQUIT;		/* '^\'	*/
412 		tio.c_cc[VERASE] = 0x08;		/* '^H'	*/
413 		tio.c_cc[VKILL]  = 'U' & 0x1f;	/* '^U'	*/
414 		tio.c_cc[VEOF]   = CEOF;		/* '^D' */
415 		tio.c_cc[VEOL]   = CEOL;		/* '^@' */
416 		tio.c_cc[VMIN]   = 4;
417 		tio.c_cc[VTIME]  = 0;
418 		tio.c_cc[VEOL2]  = CEOL;		/* '^@' */
419 		tio.c_cc[VSWTCH] = CSWTCH;		/* '^@' */
420 		tio.c_cc[VSTART] = CSTART;		/* '^S' */
421 		tio.c_cc[VSTOP]  = CSTOP;		/* '^Q' */
422 		tio.c_cc[VSUSP]  = CSUSP;		/* '^Z' */
423 
424 		/*
425 		 * change control tty.
426 		 */
427 
428 		dup2(slave, 0);
429 		dup2(slave, 1);
430 		dup2(slave, 2);
431 
432 		/* close old slave fd. */
433 		if (slave > 2)
434 			close(slave);
435 
436 		/*
437 		 * set terminal interface.
438 		 */
439 		if (tcsetattr(0, TCSANOW, &tio) == -1) {
440 			handshake.status = PTY_NG;
441 			snprintf(handshake.msg, sizeof(handshake.msg),
442 				"failed set terminal interface (TERMIOS).");
443 			send_handshake_message(terminalThread, handshake);
444 			exit(1);
445 		}
446 
447 		/*
448 		 * set window size.
449 		 */
450 
451 		handshake.status = PTY_WS;
452 		send_handshake_message(terminalThread, handshake);
453 		receive_handshake_message(handshake);
454 
455 		if (handshake.status != PTY_WS) {
456 			handshake.status = PTY_NG;
457 			snprintf(handshake.msg, sizeof(handshake.msg),
458 				"mismatch handshake.");
459 			send_handshake_message(terminalThread, handshake);
460 			exit(1);
461 		}
462 
463 		struct winsize ws;
464 
465 		ws.ws_row = handshake.row;
466 		ws.ws_col = handshake.col;
467 
468 		ioctl(0, TIOCSWINSZ, &ws);
469 
470 		tcsetpgrp(0, getpgrp());
471 			// set this process group ID as the controlling terminal
472 
473 		/* pty open and set termios successful. */
474 		handshake.status = PTY_OK;
475 		send_handshake_message(terminalThread, handshake);
476 
477 		/*
478 		 * setenv TERM and TTY.
479 		 */
480 		setenv("TERM", "beterm", true);
481 		setenv("TTY", ttyName, true);
482 		setenv("TTYPE", encoding, true);
483 		setenv("SHELL", *argv, true);
484 
485 		execve(*argv, (char * const *)argv, environ);
486 
487 		/*
488 		 * Exec failed.
489 		 */
490 		sleep(1);
491 		/*const char *spawnAlertMessage = "alert --stop "
492 						"'Cannot execute \"%s\":\n"
493 						"\t%s' "
494 						"'Use Default Shell' 'Abort'";
495 		char errorMessage[256];
496 		snprintf(errorMessage, sizeof(errorMessage), spawnAlertMessage, commandLine, strerror(errno));
497 
498 		// TODO: I'm not sure that system should return the return value of alert.
499 		// At least, it's not doing that right now.
500 		int returnValue = system(errorMessage);
501 		if (returnValue == 0)*/
502 			execl("/bin/sh", "/bin/sh", "-login", NULL);
503 
504 		exit(1);
505 	}
506 
507 	/*
508 	 * In parent Process, Set up the input and output file pointers so
509 	 * that they can write and read the pseudo terminal.
510 	 */
511 
512 	/*
513 	 * close parent control tty.
514 	 */
515 
516 	int done = 0;
517 	while (!done) {
518 		receive_handshake_message(handshake);
519 
520 		switch (handshake.status) {
521 			case PTY_OK:
522 				done = 1;
523 				break;
524 
525 			case PTY_NG:
526 				printf("%s\n", handshake.msg);
527 				done = -1;
528 				break;
529 
530 			case PTY_WS:
531 				handshake.row = row;
532 				handshake.col = col;
533 				handshake.status = PTY_WS;
534 				send_handshake_message(fProcessID, handshake);
535 				break;
536 		}
537 	}
538 
539 	if (done <= 0)
540 		return B_ERROR;
541 
542 	fFd = master;
543 
544 	return B_OK;
545 }
546 
547