//required for posix_openpt to work #define _XOPEN_SOURCE 600 #define _DEFAULT_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include "im.h" #include "escape.h" #define WRITE_BUFFER_LEN 128 //determines if the master process is running int running = 1; //pty master and slave fd int pty_slave = -1; int pty_master = -1; //freopen requires fn and not fd since its a unix function char * pty_slave_fn; //termios of stdin before we condition it, restored on process exit struct termios stdin_termio_bk; //terminal rows and cols int trows = 0; int tcols = 0; //hook for SIGWINCH void winchhook(int a) { struct winsize sz; ioctl(STDIN_FILENO, TIOCGWINSZ, &sz); trows = sz.ws_row; tcols = sz.ws_col; //trick programs into thinking the terminal is smaller than it is if (sz.ws_row >= 1) { sz.ws_row--; } ioctl(pty_slave, TIOCSWINSZ, &sz); } //child process pids pid_t shell_pid; pthread_t om_thread; pthread_t im_disp_thread; //stdout semaphore sem_t out_sem; //return 0 on success, -1 on error //sets the global vars pty_master and pty_slave int make_pty() { //creates the pty master pty_master = posix_openpt(O_RDWR); if (pty_master < 0) { fprintf(stderr, "errno: %d, failed to create pty master", errno); return errno; } //unlock the slave pty if ( unlockpt(pty_master) <0) { fprintf(stderr, "errno: %d, failed to unlock pty slave", errno); return errno; } //gets slave path pty_slave_fn = ptsname(pty_master); //tries to open pty slave pty_slave = open(pty_slave_fn, O_RDWR); if (pty_slave < 0) { fprintf(stderr, "errno: %d, failed to open pty slave '%s'",\ errno, pty_slave_fn); return errno; } //make the pty slave the same line discipline stdin was tcsetattr(pty_slave,TCSANOW,&stdin_termio_bk); return 0; } //makes stdin raw previous state stored in global var //stdin_termio_bk void condition_stdin() { struct termios stdin_termio; //save existing termios settings tcgetattr(STDIN_FILENO,&stdin_termio); tcgetattr(STDIN_FILENO,&stdin_termio_bk); //make it raw cfmakeraw(&stdin_termio); tcsetattr(STDIN_FILENO,TCSANOW,&stdin_termio); //move cursor to top left and clear screen write(STDIN_FILENO,"\033[0;0f",6); write(STDIN_FILENO,"\033[2J", 4); } void restore_stdin() { tcsetattr(STDIN_FILENO,TCSANOW,&stdin_termio_bk); } void * om_routine( void *arg) { //thread handling pty -> stdout while (running) { //output manager child process //buffer for reading from pty_master and writing to stdout char write_buffer[WRITE_BUFFER_LEN] = {0}; //only read/write syscalls work well for this, printfs //etcs do not work, this is blocking which is why its its own //thread // pty_master -> STDOUT int wchars_read = read(pty_master,write_buffer, WRITE_BUFFER_LEN); if (wchars_read >0) { sem_wait(&out_sem); out_state = update_esc_state(write_buffer, wchars_read, out_state, &out_c_parity, &out_clr); write(STDOUT_FILENO,write_buffer,wchars_read); sem_post(&out_sem); } } } void * im_disp_routine (void *args) { while (running) { // Every run of this loop, it checks to see if im_buffer has // updated, or if the screen has been cleared //then checks if the cursor save parity is right, and if the //last output tot the terminal is safe to append to if (im_updated | out_clr) { if (out_c_parity != 0) { continue; } if (safe_state(out_state)) { sem_wait(&out_sem); disp_im(STDIN_FILENO, trows, tcols); sem_post(&out_sem); im_updated = 0; out_clr = 0; } } } } //main thread exit void exit_main() { //kill shell , can not be blocked kill(shell_pid, SIGKILL); pthread_cancel(om_thread); pthread_cancel(im_disp_thread); //exit, only the master process should reach this point close(pty_master); close(pty_slave); restore_stdin(); //clears the screen so the im statusline goes away write(STDIN_FILENO,"\033[2J", 4); //resets scroll region unrestrict_scroll(STDIN_FILENO); _exit(0); } //hook for when the pty child process dies void childhook(int a) { running = 0; //this way it exists even if the loop is waiting for 1 last read char exit_main(); } int main (int argc, char * argv[], char * envp[]) { condition_stdin(); if (make_pty() != 0) { restore_stdin(); return errno; } //syncs the window sizes for the first time by manually running the //resize interrupt handler winchhook(0); //forks for running bash shell_pid = fork(); if (shell_pid == 0) { //shell child process //set self to be session leader if (setsid() <0) { fprintf(stderr, "failed to make new session\n"); } //all the stand streams freopen(pty_slave_fn, "r", stdin); freopen(pty_slave_fn, "w", stdout); freopen(pty_slave_fn, "w", stderr); //must be a session leader to set the controlling terminal if (ioctl(pty_slave, TIOCSCTTY, 0)) { fprintf(stderr, "errno: %d, failed to set controlling tty\n", errno); } /* * Opens up the Shell now */ // First checks argument, then env, then default /bin/sh char * shellpath = "/bin/sh"; if (argc >1) { shellpath = argv[1]; } else if (getenv("SHELL") != NULL) { shellpath = getenv("SHELL"); } //by convention we should set the first arg to the path to the //shell, also should be null terminated char * shell_args[2] = {shellpath, NULL}; //the process is now replaced with bash if (execve(shellpath,shell_args, envp) == -1) { fprintf(stderr, "errno: %d, Failed to start shell '%s'\n", errno, shellpath); return errno; } //code should never reach this point since we ran execve //should automatically close any file descriptors _exit(0); } else { //creates stdout semaphire if (sem_init(&out_sem, 0, 1)) { fprintf(stderr, "errno: %d, Error creating output sempahore\n", errno); exit_main(); } //creates a thread that just handles pty -> stdout transfers if (pthread_create(&om_thread, NULL, &om_routine, NULL) ) { fprintf(stderr, "errno: %d, Error creating om thread\n", errno); exit_main(); } if (pthread_create(&im_disp_thread, NULL, &im_disp_routine, NULL) ) { fprintf(stderr, "errno: %d, Error creating im display thread\n", errno); exit_main(); } //master process //registers interrupts signal(SIGCHLD, childhook); signal(SIGWINCH, winchhook); //1 char buffer for reading from stdin and writing to pty_master char read_buffer[1] = {0}; //open while (running) { int rchars_read = read(STDIN_FILENO, read_buffer, 1); if (rchars_read > 0) { //feeds input char into the escape state //machine in_state = update_esc_state(read_buffer, 1, in_state, NULL, NULL); //skips if the char was anything but //normal if (in_state == NORMAL || in_state ==DOUBLE_ESC) { //feeds the input char into the im state machine char input_buffer[IM_TABLE_ENTRY_LEN+1] = {0}; int im_chars = update_im_state(read_buffer[0], input_buffer); //just in case it wasn't null terminated right input_buffer[im_chars] = 0; if (im_chars >0) { //writes the input into pty_master if //there was an output from the state //machine write(pty_master,input_buffer,im_chars); } } else { write(pty_master,read_buffer,1); } } } } exit_main(); return 0; }