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"; 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; 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)) { 405 defaultArgs[0] = passwdStruct.pw_shell; 406 } 407 408 argv = defaultArgs; 409 argc = 2; 410 411 fShellInfo.SetDefaultShell(true); 412 } else 413 fShellInfo.SetDefaultShell(false); 414 415 fShellInfo.SetEncoding(parameters.Encoding()); 416 417 signal(SIGTTOU, SIG_IGN); 418 419 // get a pseudo-tty 420 int master = posix_openpt(O_RDWR | O_NOCTTY); 421 const char *ttyName; 422 423 if (master < 0) { 424 fprintf(stderr, "Didn't find any available pseudo ttys."); 425 return errno; 426 } 427 428 if (grantpt(master) != 0 || unlockpt(master) != 0 429 || (ttyName = ptsname(master)) == NULL) { 430 close(master); 431 fprintf(stderr, "Failed to init pseudo tty."); 432 return errno; 433 } 434 435 /* 436 * Get the modes of the current terminal. We will duplicates these 437 * on the pseudo terminal. 438 */ 439 440 thread_id terminalThread = find_thread(NULL); 441 442 /* Fork a child process. */ 443 fShellInfo.SetProcessID(fork()); 444 if (fShellInfo.ProcessID() < 0) { 445 close(master); 446 return B_ERROR; 447 } 448 449 handshake_t handshake; 450 451 if (fShellInfo.ProcessID() == 0) { 452 // Now in child process. 453 454 // close the PTY master side 455 close(master); 456 457 /* 458 * Make our controlling tty the pseudo tty. This hapens because 459 * we cleared our original controlling terminal above. 460 */ 461 462 /* Set process session leader */ 463 if (setsid() < 0) { 464 handshake.status = PTY_NG; 465 snprintf(handshake.msg, sizeof(handshake.msg), 466 "could not set session leader."); 467 send_handshake_message(terminalThread, handshake); 468 exit(1); 469 } 470 471 /* open slave pty */ 472 int slave = -1; 473 if ((slave = open(ttyName, O_RDWR)) < 0) { 474 handshake.status = PTY_NG; 475 snprintf(handshake.msg, sizeof(handshake.msg), 476 "can't open tty (%s).", ttyName); 477 send_handshake_message(terminalThread, handshake); 478 exit(1); 479 } 480 481 /* set signal default */ 482 signal(SIGCHLD, SIG_DFL); 483 signal(SIGHUP, SIG_DFL); 484 signal(SIGQUIT, SIG_DFL); 485 signal(SIGTERM, SIG_DFL); 486 signal(SIGINT, SIG_DFL); 487 signal(SIGTTOU, SIG_DFL); 488 489 struct termios tio; 490 /* get tty termios (not necessary). 491 * TODO: so why are we doing it ? 492 */ 493 tcgetattr(slave, &tio); 494 495 initialize_termios(tio); 496 497 /* 498 * change control tty. 499 */ 500 501 dup2(slave, 0); 502 dup2(slave, 1); 503 dup2(slave, 2); 504 505 /* close old slave fd. */ 506 if (slave > 2) 507 close(slave); 508 509 /* 510 * set terminal interface. 511 */ 512 if (tcsetattr(0, TCSANOW, &tio) == -1) { 513 handshake.status = PTY_NG; 514 snprintf(handshake.msg, sizeof(handshake.msg), 515 "failed set terminal interface (TERMIOS)."); 516 send_handshake_message(terminalThread, handshake); 517 exit(1); 518 } 519 520 /* 521 * set window size. 522 */ 523 524 handshake.status = PTY_WS; 525 send_handshake_message(terminalThread, handshake); 526 receive_handshake_message(handshake); 527 528 if (handshake.status != PTY_WS) { 529 handshake.status = PTY_NG; 530 snprintf(handshake.msg, sizeof(handshake.msg), 531 "mismatch handshake."); 532 send_handshake_message(terminalThread, handshake); 533 exit(1); 534 } 535 536 struct winsize ws = { handshake.row, handshake.col }; 537 538 ioctl(0, TIOCSWINSZ, &ws); 539 540 tcsetpgrp(0, getpgrp()); 541 // set this process group ID as the controlling terminal 542 set_thread_priority(find_thread(NULL), B_NORMAL_PRIORITY); 543 544 /* pty open and set termios successful. */ 545 handshake.status = PTY_OK; 546 send_handshake_message(terminalThread, handshake); 547 548 /* 549 * setenv TERM and TTY. 550 */ 551 setenv("TERM", kTerminalType, true); 552 setenv("TTY", ttyName, true); 553 setenv("TTYPE", fShellInfo.EncodingName(), true); 554 555 // set the current working directory, if one is given 556 if (parameters.CurrentDirectory().Length() > 0) 557 chdir(parameters.CurrentDirectory().String()); 558 559 execve(argv[0], (char * const *)argv, environ); 560 561 // Exec failed. 562 // TODO: This doesn't belong here. 563 564 sleep(1); 565 566 BString alertCommand = "alert --stop '"; 567 alertCommand += B_TRANSLATE("Cannot execute \"%command\":\n\t%error"); 568 alertCommand += "' '"; 569 alertCommand += B_TRANSLATE("Use default shell"); 570 alertCommand += "' '"; 571 alertCommand += B_TRANSLATE("Abort"); 572 alertCommand += "'"; 573 alertCommand.ReplaceFirst("%command", argv[0]); 574 alertCommand.ReplaceFirst("%error", strerror(errno)); 575 576 int returnValue = system(alertCommand.String()); 577 if (returnValue == 0) { 578 execl(kDefaultShell, kDefaultShell, 579 "-l", NULL); 580 } 581 582 exit(1); 583 } 584 585 /* 586 * In parent Process, Set up the input and output file pointers so 587 * that they can write and read the pseudo terminal. 588 */ 589 590 /* 591 * close parent control tty. 592 */ 593 594 int done = 0; 595 while (!done) { 596 receive_handshake_message(handshake); 597 598 switch (handshake.status) { 599 case PTY_OK: 600 done = 1; 601 break; 602 603 case PTY_NG: 604 fprintf(stderr, "%s\n", handshake.msg); 605 done = -1; 606 break; 607 608 case PTY_WS: 609 handshake.row = row; 610 handshake.col = col; 611 handshake.status = PTY_WS; 612 send_handshake_message(fShellInfo.ProcessID(), handshake); 613 break; 614 } 615 } 616 617 if (done <= 0) 618 return B_ERROR; 619 620 fFd = master; 621 622 return B_OK; 623 } 624 625