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