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