2013-02-10 65 views
0

我目前正在做一個主要爲了好玩的項目,試圖編寫一個簡單的終端模擬器。到目前爲止,我想要做的僅僅是一個「代理進程」,它只轉發終端命令。當我用下面的代碼調用/ usr/bin/nano時,這似乎工作到目前爲止。但是,當我試圖用emacs做同樣的事情時,我碰到了一個heisenbug:有時它運行良好,但大多不是:調整工作大小,但只要輸入任何內容,我的進程就會被暫停,就好像我按Ctrl + Z(我當然沒有這樣做)。寫入emacs時,進程有時會掛起

的emacs的

strace的是序列:我的過程中

ioctl(6, FIONREAD, [0])     = 0 
poll([{fd=7, events=POLLIN}], 1, 0)  = 0 (Timeout) 
read(7, 0x7fff078de1d0, 16)    = -1 EAGAIN (Resource temporarily unavailable) 
select(8, [6 7], NULL, NULL, {100000, 0}) = ? ERESTARTNOHAND (To be restarted) 
--- SIGIO (I/O possible) @ 0 (0) --- 
rt_sigreturn(0x1d)      = -1 EINTR (Interrupted system call) 

在strace的暫停時間爲:

rt_sigaction(SIGTSTP, NULL, {SIG_DFL, [], 0}, 8) = 0 
rt_sigaction(SIGTSTP, {0x7f8964468b50, [], SA_RESTORER|SA_RESTART, 0x7f8963ea54a0}, NULL, 8) = 0 
rt_sigaction(SIGINT, NULL, {SIG_DFL, [], 0}, 8) = 0 
rt_sigaction(SIGINT, {0x7f8964468a80, [], SA_RESTORER|SA_RESTART, 0x7f8963ea54a0}, NULL, 8) = 0 
rt_sigaction(SIGTERM, NULL, {SIG_DFL, [], 0}, 8) = 0 
rt_sigaction(SIGTERM, {0x7f8964468a80, [], SA_RESTORER|SA_RESTART, 0x7f8963ea54a0}, NULL, 8) = 0 
rt_sigaction(SIGWINCH, NULL, {0x401088, [WINCH], SA_RESTORER|SA_RESTART, 0x7f8963ea54a0}, 8) = 0 
ioctl(1, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig -icanon -echo ...}) = 0 
ioctl(1, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig -icanon -echo ...}) = 0 
ioctl(1, SNDCTL_TMR_STOP or TCSETSW, {B38400 opost -isig -icanon -echo ...}) = 0 
ioctl(1, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost -isig -icanon -echo ...}) = 0 
select(4, [0 3], [], NULL, NULL)  = 1 (in [0]) 
read(0, 0x7fffafd46890, 4096)   = ? ERESTARTSYS (To be restarted) 
--- SIGTTIN (Stopped (tty input)) @ 0 (0) --- 
--- SIGTTIN (Stopped (tty input)) @ 0 (0) --- 
read(0, 0x7fffafd46890, 4096)   = ? ERESTARTSYS (To be restarted) 
--- SIGTTIN (Stopped (tty input)) @ 0 (0) --- 
--- SIGTTIN (Stopped (tty input)) @ 0 (0) --- 
read(0, 0x7fffafd46890, 4096)   = ? ERESTARTSYS (To be restarted) 
--- SIGTTIN (Stopped (tty input)) @ 0 (0) --- 
--- SIGTTIN (Stopped (tty input)) @ 0 (0) --- 

我的源代碼,編譯-lutil -lncurses:

#include <assert.h> 
#include <string.h> 
#include <errno.h> 
#include <stdlib.h> 
#include <stdbool.h> 
#include <spawn.h> 
#include <fcntl.h> 
#include <sys/select.h> 
#include <stdarg.h> 
#include <unistd.h> 
#include <stdio.h> 
#include <malloc.h> 
#include <curses.h> 
#include <sys/ioctl.h> 
#include <signal.h> 


#ifdef __GNUC__ 
# define likely(x)  __builtin_expect((x),1) 
# define unlikely(x)  __builtin_expect((x),0) 
#else 
# define likely(x)  (x) 
# define unlikely(x)  (x) 
#endif 

int mpt; /* master pty */ 
pid_t pid = 0; /* child pid */ 
bool childexit = false; 

int max (int ini, ...) { 
    /* the maximum of positive integers, terminated with -1 */ 
    va_list ap; 
    va_start(ap, ini); 
    int ret = -1; 
    while (ini != -1) { 
ret = (ret < ini) ? ini : ret; 
ini = va_arg(ap, int); 
    } 
    va_end(ap); 
    return ret; 
} 
/* atexit */ 
void close_child (void) { 
    if (! childexit) { 
kill (pid, SIGTERM); 
    } 
} 
void reset_term (void) { 
    endwin(); 
} 
/* signal handlers */ 
void sigwinch (int sig) { 
    (void) sig; 
    struct winsize w; 
    if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) != -1) { 
ioctl(mpt, TIOCSWINSZ, &w); 
    } 
} 
void sigchld (int sig) { 
    (void) sig; 
    //childexit = true; 
    exit(EXIT_SUCCESS); 
} 
void sigusr1 (int sig) { 
    (void) sig; 
    struct winsize w; 
    ioctl(STDIN_FILENO, TIOCGWINSZ, &w); 
    w.ws_row = 25; 
    w.ws_col = 25; 
} 
int main (void) { 
    atexit(close_child); 
    atexit(reset_term); 
    char* spawnedArgs[] = { "/usr/bin/emacs", "-nw", NULL }; 
    char* spawnedEnv[] = { "TERM=xterm", NULL }; 
    size_t slave_len = 128 * sizeof(char); 
    char* slave = malloc (slave_len); 
    if (slave == NULL) { 
fprintf(stderr, "Cannot allocate slave var"); 
exit(EXIT_FAILURE); 
    } 
    mpt = posix_openpt(O_RDWR | O_NOCTTY); 
    if (mpt < 0) { 
perror("Cannot open Master"); 
exit(EXIT_FAILURE); 
    } 
    if (grantpt(mpt) < 0) { 
perror("Cannot grant Terminal"); 
exit(EXIT_FAILURE); 
    } 
    if (unlockpt(mpt) < 0) { 
perror("Cannot unlock Terminal"); 
exit(EXIT_FAILURE); 
    } 
    while ((ptsname_r(mpt, slave, slave_len)) != 0) { 
int e = errno; 
if (e == ERANGE) { 
    slave_len *= 2; 
    slave = realloc(slave, slave_len); 
    if (slave == NULL) { 
    fprintf(stderr, "Cannot reallocate slave var\n"); 
    exit(EXIT_FAILURE); 
    } 
} else { 
    fprintf(stderr, "Cannot get name of terminal: %s\n", strerror(e)); 
    exit(EXIT_FAILURE); 
} 
    } 
    int slavefd = open(slave, O_RDWR | O_NOCTTY); 
    if (slavefd < 0) { 
perror("Cannot open Slave"); 
exit(EXIT_FAILURE); 
    } 
    posix_spawn_file_actions_t action; 
    posix_spawnattr_t attrs; 
    sigset_t set; 
    sigemptyset(&set); 
    posix_spawnattr_setsigmask(&attrs, &set); 
    posix_spawnattr_setflags(&attrs, POSIX_SPAWN_SETSIGMASK); 
    posix_spawn_file_actions_init(&action); 
    posix_spawn_file_actions_adddup2(&action, slavefd, STDOUT_FILENO); 
    posix_spawn_file_actions_adddup2(&action, slavefd, STDIN_FILENO); 
    posix_spawnp(&pid, spawnedArgs[0], &action, &attrs, spawnedArgs, spawnedEnv); 

    fd_set rfds, wfds; 
    int sel, nfds; 

    char mystdin[4096], yourstdout[4096]; 
    int mystdindef = 0, yourstdoutdef = 0; 

    signal(SIGWINCH, sigwinch); 
    signal(SIGCHLD, sigchld); 
    signal(SIGUSR1, sigusr1); 

    initscr(); raw(); noecho(); 

    while (1) { 
    begin_select_loop: 

nfds = 0; 

FD_ZERO(&rfds); 
FD_ZERO(&wfds); 

/* read if buffers are empty, write if buffers are filled */ 

if (mystdindef == 0) { 
    FD_SET(STDIN_FILENO, &rfds); 
    nfds = max(nfds, STDIN_FILENO, -1); 
} else { 
    FD_SET(mpt, &wfds); 
    nfds = max(nfds, mpt, -1); 
} 

if (yourstdoutdef == 0) { 
    FD_SET(mpt, &rfds); 
    nfds = max(nfds, mpt, -1); 
} else { 
    FD_SET(STDOUT_FILENO, &wfds); 
    nfds = max(nfds, STDOUT_FILENO, -1); 
} 

sel = select(1 + nfds, &rfds, &wfds, NULL, NULL); 
if (sel < 0) { 
    int e = errno; 
    if (unlikely(e == EINTR)) { 
    /* A signal has interrupted us. Well, I guess this might be 
     one of the few legit uses of goto, since C99 has no good 
     mechanism for continuations yet. */ 
    goto begin_select_loop; 
    } else { 
    fprintf(stderr, "Error calling select: %s\n", strerror(e)); 
    exit(EXIT_FAILURE); 
    } 
} 

if (FD_ISSET(STDIN_FILENO, &rfds)) { 
    mystdindef = read (STDIN_FILENO, mystdin, sizeof(mystdin)); 
    if (unlikely(mystdindef == -1)) { 
    perror("Reading from stdin"); 
    exit(EXIT_FAILURE); 
    } else if (unlikely(mystdindef == 0)) { 
    /* EOF */ 
    //exit(EXIT_SUCCESS); 
    } 
} 

if (FD_ISSET(mpt, &rfds)) { 
    yourstdoutdef = read (mpt, yourstdout, sizeof(mystdin)); 
    if (unlikely(yourstdoutdef == -1)) { 
    perror("Reading from master"); 
    exit(EXIT_FAILURE); 
    } else if (unlikely(yourstdoutdef == 0)) { 
    /* EOF */ 
    //exit(EXIT_SUCCESS); 
    } 
} 

if (FD_ISSET(STDOUT_FILENO, &wfds)) { 
    int written = write(STDOUT_FILENO, yourstdout, yourstdoutdef); 
    if (unlikely(written == -1)) { 
    perror("Writing to stdout"); 
    exit(EXIT_FAILURE); 
    } else if (unlikely(written < yourstdoutdef)) { 
    memmove(yourstdout, yourstdout + written, yourstdoutdef -= written); 
    } else { 
    yourstdoutdef = 0; 
    } 
} 

if (FD_ISSET(mpt, &wfds)) { 
    int written = write(mpt, mystdin, mystdindef); 
    if (unlikely(written == -1)) { 
    perror("Writing to stdout"); 
    exit(EXIT_FAILURE); 
    } else if (unlikely(written < mystdindef)) { 
    memmove(mystdin, mystdin + written, mystdindef -= written); 
    } else { 
    mystdindef = 0; 
    } 
} 
    } 
} 

我很感激任何幫助,我真的不知道會發生什麼。

回答

1

您不通過posix_spawnattr_init()初始化posix_spawnattr_t對象(可能會導致意外的屬性被使用),也不會銷燬posix_spawnattr_tposix_spawn_file_actions_t對象。

子進程既沒有僞終端作爲它的stderr(fd 2),也沒有作爲其控制終端。因此,它將部分表現爲附加到原始tty而不是新tty。

前者很容易通過另一個dup2修復。後者需要調用setsid(),在子進程中調用非標準函數,如tcsetsid()TIOCSCTTY ioctl;因此,您需要從posix_spawnp()移至fork()execve()

如果您不介意使用更多非標準功能,可以使用login_tty()forkpty()簡化代碼。

+0

謝謝。 Forkpty讓whola的事情變得更容易。通常我不喜歡分叉,但在這種情況下,我想這基本上是最好的選擇。 – schoppenhauer 2013-02-11 00:59:04