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