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 /* default shell command and options. */ 59 const char *kDefaultShellCommand = { "/bin/sh" "-login" }; 60 61 62 /* 63 * Set environment variable. 64 */ 65 #if defined(HAIKU_TARGET_PLATFORM_BEOS) || defined(HAIKU_TARGET_PLATFORM_LIBBE_TEST) 66 67 extern char **environ; 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 152 if (fFd >= 0) { 153 close(fFd); 154 kill(-fProcessID, SIGHUP); 155 fProcessID = -1; 156 int status; 157 wait(&status); 158 fFd = -1; 159 } 160 } 161 162 163 const char * 164 Shell::TTYName() const 165 { 166 return ttyname(fFd); 167 } 168 169 170 ssize_t 171 Shell::Read(void *buffer, size_t numBytes) 172 { 173 if (fFd < 0) 174 return B_NO_INIT; 175 176 return read(fFd, buffer, numBytes); 177 } 178 179 180 ssize_t 181 Shell::Write(const void *buffer, size_t numBytes) 182 { 183 if (fFd < 0) 184 return B_NO_INIT; 185 186 return write(fFd, buffer, numBytes); 187 } 188 189 190 status_t 191 Shell::UpdateWindowSize(int rows, int columns) 192 { 193 struct winsize winSize; 194 winSize.ws_row = rows; 195 winSize.ws_col = columns; 196 ioctl(fFd, TIOCSWINSZ, &winSize); 197 return Signal(SIGWINCH); 198 } 199 200 201 status_t 202 Shell::Signal(int signal) 203 { 204 return send_signal(-fProcessID, signal); 205 } 206 207 208 status_t 209 Shell::GetAttr(struct termios &attr) 210 { 211 if (tcgetattr(fFd, &attr) < 0) 212 return errno; 213 return B_OK; 214 } 215 216 217 status_t 218 Shell::SetAttr(struct termios &attr) 219 { 220 if (tcsetattr(fFd, TCSANOW, &attr) < 0) 221 return errno; 222 return B_OK; 223 } 224 225 226 int 227 Shell::FD() const 228 { 229 return fFd; 230 } 231 232 233 void 234 Shell::ViewAttached(TermView *view) 235 { 236 if (fAttached) 237 return; 238 239 status_t status = fTermParse->StartThreads(view); 240 if (status < B_OK) { 241 // TODO: What can we do here ? 242 fprintf(stderr, "Shell:ViewAttached():" 243 " cannot start parser threads: %s", 244 strerror(status)); 245 } 246 } 247 248 249 void 250 Shell::ViewDetached() 251 { 252 if (fAttached) 253 fTermParse->StopThreads(); 254 } 255 256 257 // private 258 static status_t 259 send_handshake_message(thread_id target, const handshake_t& handshake) 260 { 261 return send_data(target, 0, &handshake, sizeof(handshake_t)); 262 } 263 264 265 static void 266 receive_handshake_message(handshake_t& handshake) 267 { 268 thread_id sender; 269 receive_data(&sender, &handshake, sizeof(handshake_t)); 270 } 271 272 273 status_t 274 Shell::_Spawn(int row, int col, const char *encoding, int argc, const char **argv) 275 { 276 signal(SIGTTOU, SIG_IGN); 277 278 /* 279 * Get a pseudo-tty. We do this by cycling through files in the 280 * directory. The operationg system will not allow us to open a master 281 * which is already in use, so we simply go until the open succeeds. 282 */ 283 char ttyName[B_PATH_NAME_LENGTH]; 284 int master = -1; 285 DIR *dir = opendir("/dev/pt/"); 286 if (dir != NULL) { 287 struct dirent *dirEntry; 288 while ((dirEntry = readdir(dir)) != NULL) { 289 // skip '.' and '..' 290 if (dirEntry->d_name[0] == '.') 291 continue; 292 293 char ptyName[B_PATH_NAME_LENGTH]; 294 snprintf(ptyName, sizeof(ptyName), "/dev/pt/%s", dirEntry->d_name); 295 296 master = open(ptyName, O_RDWR); 297 if (master >= 0) { 298 // Set the tty that corresponds to the pty we found 299 snprintf(ttyName, sizeof(ttyName), "/dev/tt/%s", dirEntry->d_name); 300 break; 301 } else { 302 // B_BUSY is a normal case 303 if (errno != B_BUSY) 304 fprintf(stderr, "could not open %s: %s\n", ptyName, strerror(errno)); 305 } 306 } 307 closedir(dir); 308 } 309 310 if (master < 0) { 311 printf("didn't find any available pseudo ttys."); 312 return B_ERROR; 313 } 314 315 /* 316 * Get the modes of the current terminal. We will duplicates these 317 * on the pseudo terminal. 318 */ 319 320 thread_id terminalThread = find_thread(NULL); 321 322 /* Fork a child process. */ 323 if ((fProcessID = fork()) < 0) { 324 close(master); 325 return B_ERROR; 326 } 327 328 handshake_t handshake; 329 330 if (fProcessID == 0) { 331 // Now in child process. 332 333 /* 334 * Make our controlling tty the pseudo tty. This hapens because 335 * we cleared our original controlling terminal above. 336 */ 337 338 /* Set process session leader */ 339 if (setsid() < 0) { 340 handshake.status = PTY_NG; 341 snprintf(handshake.msg, sizeof(handshake.msg), 342 "could not set session leader."); 343 send_handshake_message(terminalThread, handshake); 344 exit(1); 345 } 346 347 /* change pty owner and access mode. */ 348 chown(ttyName, getuid(), getgid()); 349 chmod(ttyName, S_IRUSR | S_IWUSR); 350 351 /* open slave pty */ 352 int slave = -1; 353 if ((slave = open(ttyName, O_RDWR)) < 0) { 354 handshake.status = PTY_NG; 355 snprintf(handshake.msg, sizeof(handshake.msg), 356 "can't open tty (%s).", ttyName); 357 send_handshake_message(terminalThread, handshake); 358 exit(1); 359 } 360 361 /* set signal default */ 362 signal(SIGCHLD, SIG_DFL); 363 signal(SIGHUP, SIG_DFL); 364 signal(SIGQUIT, SIG_DFL); 365 signal(SIGTERM, SIG_DFL); 366 signal(SIGINT, SIG_DFL); 367 signal(SIGTTOU, SIG_DFL); 368 369 struct termios tio; 370 /* get tty termios (not necessary). 371 * TODO: so why are we doing it ? 372 */ 373 tcgetattr(slave, &tio); 374 375 /* 376 * Set Terminal interface. 377 */ 378 379 tio.c_line = 0; 380 tio.c_lflag |= ECHOE; 381 382 /* input: nl->nl, cr->nl */ 383 tio.c_iflag &= ~(INLCR|IGNCR); 384 tio.c_iflag |= ICRNL; 385 tio.c_iflag &= ~ISTRIP; 386 387 /* output: cr->cr, nl in not retrun, no delays, ln->cr/ln */ 388 tio.c_oflag &= ~(OCRNL|ONLRET|NLDLY|CRDLY|TABDLY|BSDLY|VTDLY|FFDLY); 389 tio.c_oflag |= ONLCR; 390 tio.c_oflag |= OPOST; 391 392 /* baud rate is 19200 (equal beterm) */ 393 tio.c_cflag &= ~(CBAUD); 394 tio.c_cflag |= B19200; 395 396 tio.c_cflag &= ~CSIZE; 397 tio.c_cflag |= CS8; 398 tio.c_cflag |= CREAD; 399 400 tio.c_cflag |= HUPCL; 401 tio.c_iflag &= ~(IGNBRK|BRKINT); 402 403 /* 404 * enable signals, canonical processing (erase, kill, etc), echo. 405 */ 406 tio.c_lflag |= ISIG|ICANON|ECHO|ECHOE|ECHONL; 407 tio.c_lflag &= ~(ECHOK | IEXTEN); 408 409 /* set control characters. */ 410 tio.c_cc[VINTR] = 'C' & 0x1f; /* '^C' */ 411 tio.c_cc[VQUIT] = CQUIT; /* '^\' */ 412 tio.c_cc[VERASE] = 0x08; /* '^H' */ 413 tio.c_cc[VKILL] = 'U' & 0x1f; /* '^U' */ 414 tio.c_cc[VEOF] = CEOF; /* '^D' */ 415 tio.c_cc[VEOL] = CEOL; /* '^@' */ 416 tio.c_cc[VMIN] = 4; 417 tio.c_cc[VTIME] = 0; 418 tio.c_cc[VEOL2] = CEOL; /* '^@' */ 419 tio.c_cc[VSWTCH] = CSWTCH; /* '^@' */ 420 tio.c_cc[VSTART] = CSTART; /* '^S' */ 421 tio.c_cc[VSTOP] = CSTOP; /* '^Q' */ 422 tio.c_cc[VSUSP] = CSUSP; /* '^Z' */ 423 424 /* 425 * change control tty. 426 */ 427 428 dup2(slave, 0); 429 dup2(slave, 1); 430 dup2(slave, 2); 431 432 /* close old slave fd. */ 433 if (slave > 2) 434 close(slave); 435 436 /* 437 * set terminal interface. 438 */ 439 if (tcsetattr(0, TCSANOW, &tio) == -1) { 440 handshake.status = PTY_NG; 441 snprintf(handshake.msg, sizeof(handshake.msg), 442 "failed set terminal interface (TERMIOS)."); 443 send_handshake_message(terminalThread, handshake); 444 exit(1); 445 } 446 447 /* 448 * set window size. 449 */ 450 451 handshake.status = PTY_WS; 452 send_handshake_message(terminalThread, handshake); 453 receive_handshake_message(handshake); 454 455 if (handshake.status != PTY_WS) { 456 handshake.status = PTY_NG; 457 snprintf(handshake.msg, sizeof(handshake.msg), 458 "mismatch handshake."); 459 send_handshake_message(terminalThread, handshake); 460 exit(1); 461 } 462 463 struct winsize ws; 464 465 ws.ws_row = handshake.row; 466 ws.ws_col = handshake.col; 467 468 ioctl(0, TIOCSWINSZ, &ws); 469 470 tcsetpgrp(0, getpgrp()); 471 // set this process group ID as the controlling terminal 472 473 /* pty open and set termios successful. */ 474 handshake.status = PTY_OK; 475 send_handshake_message(terminalThread, handshake); 476 477 /* 478 * setenv TERM and TTY. 479 */ 480 setenv("TERM", "beterm", true); 481 setenv("TTY", ttyName, true); 482 setenv("TTYPE", encoding, true); 483 setenv("SHELL", *argv, true); 484 485 execve(*argv, (char * const *)argv, environ); 486 487 /* 488 * Exec failed. 489 */ 490 sleep(1); 491 /*const char *spawnAlertMessage = "alert --stop " 492 "'Cannot execute \"%s\":\n" 493 "\t%s' " 494 "'Use Default Shell' 'Abort'"; 495 char errorMessage[256]; 496 snprintf(errorMessage, sizeof(errorMessage), spawnAlertMessage, commandLine, strerror(errno)); 497 498 // TODO: I'm not sure that system should return the return value of alert. 499 // At least, it's not doing that right now. 500 int returnValue = system(errorMessage); 501 if (returnValue == 0)*/ 502 execl("/bin/sh", "/bin/sh", "-login", NULL); 503 504 exit(1); 505 } 506 507 /* 508 * In parent Process, Set up the input and output file pointers so 509 * that they can write and read the pseudo terminal. 510 */ 511 512 /* 513 * close parent control tty. 514 */ 515 516 int done = 0; 517 while (!done) { 518 receive_handshake_message(handshake); 519 520 switch (handshake.status) { 521 case PTY_OK: 522 done = 1; 523 break; 524 525 case PTY_NG: 526 printf("%s\n", handshake.msg); 527 done = -1; 528 break; 529 530 case PTY_WS: 531 handshake.row = row; 532 handshake.col = col; 533 handshake.status = PTY_WS; 534 send_handshake_message(fProcessID, handshake); 535 break; 536 } 537 } 538 539 if (done <= 0) 540 return B_ERROR; 541 542 fFd = master; 543 544 return B_OK; 545 } 546 547