//required for posix_openpt to work #define _XOPEN_SOURCE 600 #define _DEFAULT_SOURCE #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; pid_t om_pid; //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); } void restore_stdin() { tcsetattr(STDIN_FILENO,TCSANOW,&stdin_termio_bk); } //main thread exit void exit_main() { //kill shell , can not be blocked kill(shell_pid, SIGKILL); //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); _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 { //forks for read and write, master process handles im, child //process just handles transfering pty output to stdout om_pid = fork(); if (om_pid == 0) { //sets the name for cleaner ps output prctl(PR_SET_NAME, "ptyim om thread"); //output manager child process //runs forever until SIGKILL'd while (true) { //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 // pty_master -> STDOUT int wchars_read = read(pty_master,write_buffer, WRITE_BUFFER_LEN); if (wchars_read >0) { write(STDOUT_FILENO,write_buffer,wchars_read); } // stdin -> pty_master, im logic is also here } //child process exit _exit(0); } else { //master process //registers interrupts signal(SIGCHLD, childhook); signal(SIGWINCH, winchhook); //display the im for the first time disp_im(STDIN_FILENO, trows, tcols); //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); fprintf(stderr, "%c: %d\n", read_buffer[0],in_state); //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); } //any input update will lead to a disp_im update disp_im(STDIN_FILENO, trows, tcols); } } //at the end of the main process, kil lthe om process kill(om_pid, SIGKILL); } } exit_main(); return 0; }