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