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(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 status_t 233 Shell::AttachBuffer(TerminalBuffer *buffer) 234 { 235 if (fAttached) 236 return B_ERROR; 237 238 fAttached = true; 239 240 return fTermParse->StartThreads(buffer); 241 } 242 243 244 void 245 Shell::DetachBuffer() 246 { 247 if (fAttached) 248 fTermParse->StopThreads(); 249 } 250 251 252 // private 253 static status_t 254 send_handshake_message(thread_id target, const handshake_t& handshake) 255 { 256 return send_data(target, 0, &handshake, sizeof(handshake_t)); 257 } 258 259 260 static void 261 receive_handshake_message(handshake_t& handshake) 262 { 263 thread_id sender; 264 receive_data(&sender, &handshake, sizeof(handshake_t)); 265 } 266 267 268 static void 269 initialize_termios(struct termios &tio) 270 { 271 /* 272 * Set Terminal interface. 273 */ 274 275 tio.c_line = 0; 276 tio.c_lflag |= ECHOE; 277 278 /* input: nl->nl, cr->nl */ 279 tio.c_iflag &= ~(INLCR|IGNCR); 280 tio.c_iflag |= ICRNL; 281 tio.c_iflag &= ~ISTRIP; 282 283 /* output: cr->cr, nl in not retrun, no delays, ln->cr/ln */ 284 tio.c_oflag &= ~(OCRNL|ONLRET|NLDLY|CRDLY|TABDLY|BSDLY|VTDLY|FFDLY); 285 tio.c_oflag |= ONLCR; 286 tio.c_oflag |= OPOST; 287 288 /* baud rate is 19200 (equal beterm) */ 289 tio.c_cflag &= ~(CBAUD); 290 tio.c_cflag |= B19200; 291 292 tio.c_cflag &= ~CSIZE; 293 tio.c_cflag |= CS8; 294 tio.c_cflag |= CREAD; 295 296 tio.c_cflag |= HUPCL; 297 tio.c_iflag &= ~(IGNBRK|BRKINT); 298 299 /* 300 * enable signals, canonical processing (erase, kill, etc), echo. 301 */ 302 tio.c_lflag |= ISIG|ICANON|ECHO|ECHOE|ECHONL; 303 tio.c_lflag &= ~(ECHOK | IEXTEN); 304 305 /* set control characters. */ 306 tio.c_cc[VINTR] = 'C' & 0x1f; /* '^C' */ 307 tio.c_cc[VQUIT] = CQUIT; /* '^\' */ 308 tio.c_cc[VERASE] = 0x7f; /* '^?' */ 309 tio.c_cc[VKILL] = 'U' & 0x1f; /* '^U' */ 310 tio.c_cc[VEOF] = CEOF; /* '^D' */ 311 tio.c_cc[VEOL] = CEOL; /* '^@' */ 312 tio.c_cc[VMIN] = 4; 313 tio.c_cc[VTIME] = 0; 314 tio.c_cc[VEOL2] = CEOL; /* '^@' */ 315 tio.c_cc[VSWTCH] = CSWTCH; /* '^@' */ 316 tio.c_cc[VSTART] = CSTART; /* '^S' */ 317 tio.c_cc[VSTOP] = CSTOP; /* '^Q' */ 318 tio.c_cc[VSUSP] = CSUSP; /* '^Z' */ 319 } 320 321 #undef B_TRANSLATE_CONTEXT 322 #define B_TRANSLATE_CONTEXT "Terminal Shell" 323 324 status_t 325 Shell::_Spawn(int row, int col, const char *encoding, int argc, const char **argv) 326 { 327 const char *kDefaultShellCommand[] = { "/bin/sh", "--login", NULL }; 328 329 if (argv == NULL || argc == 0) { 330 argv = kDefaultShellCommand; 331 argc = 2; 332 } 333 334 signal(SIGTTOU, SIG_IGN); 335 336 #ifdef __HAIKU__ 337 // get a pseudo-tty 338 int master = posix_openpt(O_RDWR | O_NOCTTY); 339 const char *ttyName; 340 #else /* __HAIKU__ */ 341 /* 342 * Get a pseudo-tty. We do this by cycling through files in the 343 * directory. The operating system will not allow us to open a master 344 * which is already in use, so we simply go until the open succeeds. 345 */ 346 char ttyName[B_PATH_NAME_LENGTH]; 347 int master = -1; 348 DIR *dir = opendir("/dev/pt/"); 349 if (dir != NULL) { 350 struct dirent *dirEntry; 351 while ((dirEntry = readdir(dir)) != NULL) { 352 // skip '.' and '..' 353 if (dirEntry->d_name[0] == '.') 354 continue; 355 356 char ptyName[B_PATH_NAME_LENGTH]; 357 snprintf(ptyName, sizeof(ptyName), "/dev/pt/%s", dirEntry->d_name); 358 359 master = open(ptyName, O_RDWR); 360 if (master >= 0) { 361 // Set the tty that corresponds to the pty we found 362 snprintf(ttyName, sizeof(ttyName), "/dev/tt/%s", dirEntry->d_name); 363 break; 364 } else { 365 // B_BUSY is a normal case 366 if (errno != B_BUSY) 367 fprintf(stderr, B_TRANSLATE("could not open %s: %s\n"), 368 ptyName, strerror(errno)); 369 } 370 } 371 closedir(dir); 372 } 373 #endif /* __HAIKU__ */ 374 375 if (master < 0) { 376 fprintf(stderr, B_TRANSLATE("Didn't find any available pseudo ttys.")); 377 return errno; 378 } 379 380 #ifdef __HAIKU__ 381 if (grantpt(master) != 0 || unlockpt(master) != 0 382 || (ttyName = ptsname(master)) == NULL) { 383 close(master); 384 fprintf(stderr, B_TRANSLATE("Failed to init pseudo tty.")); 385 return errno; 386 } 387 #endif /* __HAIKU__ */ 388 389 /* 390 * Get the modes of the current terminal. We will duplicates these 391 * on the pseudo terminal. 392 */ 393 394 thread_id terminalThread = find_thread(NULL); 395 396 /* Fork a child process. */ 397 if ((fProcessID = fork()) < 0) { 398 close(master); 399 return B_ERROR; 400 } 401 402 handshake_t handshake; 403 404 if (fProcessID == 0) { 405 // Now in child process. 406 407 // close the PTY master side 408 close(master); 409 410 /* 411 * Make our controlling tty the pseudo tty. This hapens because 412 * we cleared our original controlling terminal above. 413 */ 414 415 /* Set process session leader */ 416 if (setsid() < 0) { 417 handshake.status = PTY_NG; 418 snprintf(handshake.msg, sizeof(handshake.msg), 419 B_TRANSLATE("could not set session leader.")); 420 send_handshake_message(terminalThread, handshake); 421 exit(1); 422 } 423 424 /* open slave pty */ 425 int slave = -1; 426 if ((slave = open(ttyName, O_RDWR)) < 0) { 427 handshake.status = PTY_NG; 428 snprintf(handshake.msg, sizeof(handshake.msg), 429 B_TRANSLATE("can't open tty (%s)."), ttyName); 430 send_handshake_message(terminalThread, handshake); 431 exit(1); 432 } 433 434 /* set signal default */ 435 signal(SIGCHLD, SIG_DFL); 436 signal(SIGHUP, SIG_DFL); 437 signal(SIGQUIT, SIG_DFL); 438 signal(SIGTERM, SIG_DFL); 439 signal(SIGINT, SIG_DFL); 440 signal(SIGTTOU, SIG_DFL); 441 442 struct termios tio; 443 /* get tty termios (not necessary). 444 * TODO: so why are we doing it ? 445 */ 446 tcgetattr(slave, &tio); 447 448 initialize_termios(tio); 449 450 /* 451 * change control tty. 452 */ 453 454 dup2(slave, 0); 455 dup2(slave, 1); 456 dup2(slave, 2); 457 458 /* close old slave fd. */ 459 if (slave > 2) 460 close(slave); 461 462 /* 463 * set terminal interface. 464 */ 465 if (tcsetattr(0, TCSANOW, &tio) == -1) { 466 handshake.status = PTY_NG; 467 snprintf(handshake.msg, sizeof(handshake.msg), 468 B_TRANSLATE("failed set terminal interface (TERMIOS).")); 469 send_handshake_message(terminalThread, handshake); 470 exit(1); 471 } 472 473 /* 474 * set window size. 475 */ 476 477 handshake.status = PTY_WS; 478 send_handshake_message(terminalThread, handshake); 479 receive_handshake_message(handshake); 480 481 if (handshake.status != PTY_WS) { 482 handshake.status = PTY_NG; 483 snprintf(handshake.msg, sizeof(handshake.msg), 484 B_TRANSLATE("mismatch handshake.")); 485 send_handshake_message(terminalThread, handshake); 486 exit(1); 487 } 488 489 struct winsize ws = { handshake.row, handshake.col }; 490 491 ioctl(0, TIOCSWINSZ, &ws); 492 493 tcsetpgrp(0, getpgrp()); 494 // set this process group ID as the controlling terminal 495 #ifndef __HAIKU__ 496 ioctl(0, 'pgid', getpid()); 497 #endif 498 set_thread_priority(find_thread(NULL), B_NORMAL_PRIORITY); 499 500 /* pty open and set termios successful. */ 501 handshake.status = PTY_OK; 502 send_handshake_message(terminalThread, handshake); 503 504 /* 505 * setenv TERM and TTY. 506 */ 507 setenv("TERM", "xterm", true); 508 setenv("TTY", ttyName, true); 509 setenv("TTYPE", encoding, true); 510 setenv("SHELL", argv[0], true); 511 512 execve(argv[0], (char * const *)argv, environ); 513 514 // Exec failed. 515 // TODO: This doesn't belong here. 516 517 sleep(1); 518 519 BString alertCommand = "alert --stop '"; 520 alertCommand += B_TRANSLATE("Cannot execute \"%command\":\n\t%error"); 521 alertCommand += "' '"; 522 alertCommand += B_TRANSLATE("Use default shell"); 523 alertCommand += "' '"; 524 alertCommand += B_TRANSLATE("Abort"); 525 alertCommand += "'"; 526 alertCommand.ReplaceFirst("%command", argv[0]); 527 alertCommand.ReplaceFirst("%error", strerror(errno)); 528 529 int returnValue = system(alertCommand.String()); 530 if (returnValue == 0) { 531 execl(kDefaultShellCommand[0], kDefaultShellCommand[0], 532 kDefaultShellCommand[1], NULL); 533 } 534 535 exit(1); 536 } 537 538 /* 539 * In parent Process, Set up the input and output file pointers so 540 * that they can write and read the pseudo terminal. 541 */ 542 543 /* 544 * close parent control tty. 545 */ 546 547 int done = 0; 548 while (!done) { 549 receive_handshake_message(handshake); 550 551 switch (handshake.status) { 552 case PTY_OK: 553 done = 1; 554 break; 555 556 case PTY_NG: 557 fprintf(stderr, "%s\n", handshake.msg); 558 done = -1; 559 break; 560 561 case PTY_WS: 562 handshake.row = row; 563 handshake.col = col; 564 handshake.status = PTY_WS; 565 send_handshake_message(fProcessID, handshake); 566 break; 567 } 568 } 569 570 if (done <= 0) 571 return B_ERROR; 572 573 fFd = master; 574 575 return B_OK; 576 } 577 578