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 71 const 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 80 extern char **environ; 81 82 static int setenv(const char *var, const char *value, bool overwrite); 83 84 static int 85 setenv(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 */ 112 typedef 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 126 Shell::Shell() 127 : 128 fFd(-1), 129 fProcessID(-1), 130 fTermParse(NULL), 131 fAttached(false) 132 { 133 } 134 135 136 Shell::~Shell() 137 { 138 Close(); 139 } 140 141 142 status_t 143 Shell::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 162 void 163 Shell::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 179 const char * 180 Shell::TTYName() const 181 { 182 return ttyname(fFd); 183 } 184 185 186 ssize_t 187 Shell::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 196 ssize_t 197 Shell::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 206 status_t 207 Shell::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 218 status_t 219 Shell::GetAttr(struct termios &attr) const 220 { 221 if (tcgetattr(fFd, &attr) < 0) 222 return errno; 223 return B_OK; 224 } 225 226 227 status_t 228 Shell::SetAttr(const struct termios &attr) 229 { 230 if (tcsetattr(fFd, TCSANOW, &attr) < 0) 231 return errno; 232 return B_OK; 233 } 234 235 236 int 237 Shell::FD() const 238 { 239 return fFd; 240 } 241 242 243 bool 244 Shell::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 254 bool 255 Shell::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 293 status_t 294 Shell::AttachBuffer(TerminalBuffer *buffer) 295 { 296 if (fAttached) 297 return B_ERROR; 298 299 fAttached = true; 300 301 return fTermParse->StartThreads(buffer); 302 } 303 304 305 void 306 Shell::DetachBuffer() 307 { 308 if (fAttached) 309 fTermParse->StopThreads(); 310 } 311 312 313 // private 314 static status_t 315 send_handshake_message(thread_id target, const handshake_t& handshake) 316 { 317 return send_data(target, 0, &handshake, sizeof(handshake_t)); 318 } 319 320 321 static void 322 receive_handshake_message(handshake_t& handshake) 323 { 324 thread_id sender; 325 receive_data(&sender, &handshake, sizeof(handshake_t)); 326 } 327 328 329 static void 330 initialize_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 385 status_t 386 Shell::_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", 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