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