1 /* 2 * Copyright 2007-2009, 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 #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) const 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) const 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 status_t 230 Shell::AttachBuffer(TerminalBuffer *buffer) 231 { 232 if (fAttached) 233 return B_ERROR; 234 235 fAttached = true; 236 237 return fTermParse->StartThreads(buffer); 238 } 239 240 241 void 242 Shell::DetachBuffer() 243 { 244 if (fAttached) 245 fTermParse->StopThreads(); 246 } 247 248 249 // private 250 static status_t 251 send_handshake_message(thread_id target, const handshake_t& handshake) 252 { 253 return send_data(target, 0, &handshake, sizeof(handshake_t)); 254 } 255 256 257 static void 258 receive_handshake_message(handshake_t& handshake) 259 { 260 thread_id sender; 261 receive_data(&sender, &handshake, sizeof(handshake_t)); 262 } 263 264 265 static void 266 initialize_termios(struct termios &tio) 267 { 268 /* 269 * Set Terminal interface. 270 */ 271 272 tio.c_line = 0; 273 tio.c_lflag |= ECHOE; 274 275 /* input: nl->nl, cr->nl */ 276 tio.c_iflag &= ~(INLCR|IGNCR); 277 tio.c_iflag |= ICRNL; 278 tio.c_iflag &= ~ISTRIP; 279 280 /* output: cr->cr, nl in not retrun, no delays, ln->cr/ln */ 281 tio.c_oflag &= ~(OCRNL|ONLRET|NLDLY|CRDLY|TABDLY|BSDLY|VTDLY|FFDLY); 282 tio.c_oflag |= ONLCR; 283 tio.c_oflag |= OPOST; 284 285 /* baud rate is 19200 (equal beterm) */ 286 tio.c_cflag &= ~(CBAUD); 287 tio.c_cflag |= B19200; 288 289 tio.c_cflag &= ~CSIZE; 290 tio.c_cflag |= CS8; 291 tio.c_cflag |= CREAD; 292 293 tio.c_cflag |= HUPCL; 294 tio.c_iflag &= ~(IGNBRK|BRKINT); 295 296 /* 297 * enable signals, canonical processing (erase, kill, etc), echo. 298 */ 299 tio.c_lflag |= ISIG|ICANON|ECHO|ECHOE|ECHONL; 300 tio.c_lflag &= ~(ECHOK | IEXTEN); 301 302 /* set control characters. */ 303 tio.c_cc[VINTR] = 'C' & 0x1f; /* '^C' */ 304 tio.c_cc[VQUIT] = CQUIT; /* '^\' */ 305 tio.c_cc[VERASE] = 0x7f; /* '^?' */ 306 tio.c_cc[VKILL] = 'U' & 0x1f; /* '^U' */ 307 tio.c_cc[VEOF] = CEOF; /* '^D' */ 308 tio.c_cc[VEOL] = CEOL; /* '^@' */ 309 tio.c_cc[VMIN] = 4; 310 tio.c_cc[VTIME] = 0; 311 tio.c_cc[VEOL2] = CEOL; /* '^@' */ 312 tio.c_cc[VSWTCH] = CSWTCH; /* '^@' */ 313 tio.c_cc[VSTART] = CSTART; /* '^S' */ 314 tio.c_cc[VSTOP] = CSTOP; /* '^Q' */ 315 tio.c_cc[VSUSP] = CSUSP; /* '^Z' */ 316 } 317 318 319 status_t 320 Shell::_Spawn(int row, int col, const char *encoding, int argc, const char **argv) 321 { 322 const char *kDefaultShellCommand[] = { "/bin/sh", "--login", NULL }; 323 324 if (argv == NULL || argc == 0) { 325 argv = kDefaultShellCommand; 326 argc = 2; 327 } 328 329 signal(SIGTTOU, SIG_IGN); 330 331 #ifdef __HAIKU__ 332 // get a pseudo-tty 333 int master = posix_openpt(O_RDWR | O_NOCTTY); 334 const char *ttyName; 335 #else /* __HAIKU__ */ 336 /* 337 * Get a pseudo-tty. We do this by cycling through files in the 338 * directory. The operating system will not allow us to open a master 339 * which is already in use, so we simply go until the open succeeds. 340 */ 341 char ttyName[B_PATH_NAME_LENGTH]; 342 int master = -1; 343 DIR *dir = opendir("/dev/pt/"); 344 if (dir != NULL) { 345 struct dirent *dirEntry; 346 while ((dirEntry = readdir(dir)) != NULL) { 347 // skip '.' and '..' 348 if (dirEntry->d_name[0] == '.') 349 continue; 350 351 char ptyName[B_PATH_NAME_LENGTH]; 352 snprintf(ptyName, sizeof(ptyName), "/dev/pt/%s", dirEntry->d_name); 353 354 master = open(ptyName, O_RDWR); 355 if (master >= 0) { 356 // Set the tty that corresponds to the pty we found 357 snprintf(ttyName, sizeof(ttyName), "/dev/tt/%s", dirEntry->d_name); 358 break; 359 } else { 360 // B_BUSY is a normal case 361 if (errno != B_BUSY) 362 fprintf(stderr, "could not open %s: %s\n", ptyName, strerror(errno)); 363 } 364 } 365 closedir(dir); 366 } 367 #endif /* __HAIKU__ */ 368 369 if (master < 0) { 370 fprintf(stderr, "Didn't find any available pseudo ttys."); 371 return errno; 372 } 373 374 #ifdef __HAIKU__ 375 if (grantpt(master) != 0 || unlockpt(master) != 0 376 || (ttyName = ptsname(master)) == NULL) { 377 close(master); 378 fprintf(stderr, "Failed to init pseudo tty."); 379 return errno; 380 } 381 #endif /* __HAIKU__ */ 382 383 /* 384 * Get the modes of the current terminal. We will duplicates these 385 * on the pseudo terminal. 386 */ 387 388 thread_id terminalThread = find_thread(NULL); 389 390 /* Fork a child process. */ 391 if ((fProcessID = fork()) < 0) { 392 close(master); 393 return B_ERROR; 394 } 395 396 handshake_t handshake; 397 398 if (fProcessID == 0) { 399 // Now in child process. 400 401 /* 402 * Make our controlling tty the pseudo tty. This hapens because 403 * we cleared our original controlling terminal above. 404 */ 405 406 /* Set process session leader */ 407 if (setsid() < 0) { 408 handshake.status = PTY_NG; 409 snprintf(handshake.msg, sizeof(handshake.msg), 410 "could not set session leader."); 411 send_handshake_message(terminalThread, handshake); 412 exit(1); 413 } 414 415 /* open slave pty */ 416 int slave = -1; 417 if ((slave = open(ttyName, O_RDWR)) < 0) { 418 handshake.status = PTY_NG; 419 snprintf(handshake.msg, sizeof(handshake.msg), 420 "can't open tty (%s).", ttyName); 421 send_handshake_message(terminalThread, handshake); 422 exit(1); 423 } 424 425 /* set signal default */ 426 signal(SIGCHLD, SIG_DFL); 427 signal(SIGHUP, SIG_DFL); 428 signal(SIGQUIT, SIG_DFL); 429 signal(SIGTERM, SIG_DFL); 430 signal(SIGINT, SIG_DFL); 431 signal(SIGTTOU, SIG_DFL); 432 433 struct termios tio; 434 /* get tty termios (not necessary). 435 * TODO: so why are we doing it ? 436 */ 437 tcgetattr(slave, &tio); 438 439 initialize_termios(tio); 440 441 /* 442 * change control tty. 443 */ 444 445 dup2(slave, 0); 446 dup2(slave, 1); 447 dup2(slave, 2); 448 449 /* close old slave fd. */ 450 if (slave > 2) 451 close(slave); 452 453 /* 454 * set terminal interface. 455 */ 456 if (tcsetattr(0, TCSANOW, &tio) == -1) { 457 handshake.status = PTY_NG; 458 snprintf(handshake.msg, sizeof(handshake.msg), 459 "failed set terminal interface (TERMIOS)."); 460 send_handshake_message(terminalThread, handshake); 461 exit(1); 462 } 463 464 /* 465 * set window size. 466 */ 467 468 handshake.status = PTY_WS; 469 send_handshake_message(terminalThread, handshake); 470 receive_handshake_message(handshake); 471 472 if (handshake.status != PTY_WS) { 473 handshake.status = PTY_NG; 474 snprintf(handshake.msg, sizeof(handshake.msg), 475 "mismatch handshake."); 476 send_handshake_message(terminalThread, handshake); 477 exit(1); 478 } 479 480 struct winsize ws = { handshake.row, 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 * TODO: This doesn't belong here. 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