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