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