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