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