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