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