Search code examples
cstdoutstdinctrl

Exit read listening when pressing "Ctrl + c" in C


I'm rewriting a shell in C and I fell on a problem.

When writing a command — for example echo "this — we got a new prompt ("dquote>", in zsh) and we can exit it with "Ctrl + c" and get back to our last command prompt.

I'm stuck there; I just can't get out of my read function (listen on "dquote>"), I tried to write on stdout an EOF when pressing "ctrl + c" but it doesn't read it.

I switched to non-canonical mode. I catch signal with signal(SIGINT, sig_hand);

then i execute this part of code when signal is catched:

static void sig_hand(int sig)
{
    if (g_shell.is_listen_bracket) // if is the first prompt or no
        putchar(4); // EOT
    else
    {
        putstr("\n");
        print_prompt();
    }
}

and my read function:

int     j;
char    command[ARG_MAX];
char    buff[3];

j = -1;
while (1)
{
    bzero(buff, 3);
    read(0, buff, 3);
    if (buff[0] == 4 && !buff[1] && !buff[2])
        return (ctrl_d(shell));
    else if (isprint(buff[0]) && !buff[1] && !buff[2]) // if is between 32 and 126 (ascii)
    {
        command[++j] = buff[0];
        putchar(buff[0]);
    }
}
command[++j] = '\0';
return (strdup(command));

So my code waiting on "read(0, buff, 3);", and i want to quit it when pressing ctrl + c.

Thanks for helping !


Solution

  • Don't think of EOF as a character that you can 'print to stdout', it is a state that the file handle can be in, and it implies that there is no more data coming. To put your stdout into the EOF state, you would have to call close() - which is most likely not what you want.

    Watch out - 0x04 is actually EOT, End of Transmission.

    In any case, why do you want to send EOT to the stdout of your application? If this behaved the way it would appear you think it does, then the terminal emulator (or whatever is connected to your stdout) would quit - not your shell, and it certainly wouldn't revert your shell from the 'waiting for more input' state to the 'waiting for input' state.

    You need to handle the ^C in the signal handler, and adjust your shell's state appropriately, getting it to abandon the current input mode and redraw the basic prompt.


    Edit: What you need to remember is that your 'shell' is writing text (output) and control characters to the terminal emulator - the terminal emulator is writing text (input) and control characters to your 'shell'.

    If you want to revert the prompt from dquote> to mysh$, then you must update the terminal by writing a new prompt to it.

    To keep track of what you are currently doing, it might make most sense to use the State Machine approach. You might have a handful of states, including:

    1. INPUT_WAITING
    2. INPUT_WAITING_CONT
    3. COMMAND_RUN

    When in the INPUT_WAITING state, you would print the mysh$ prompt, and handle input. When a newline is received, you would then decide 'do we have all of the information?' before advancing to the INPUT_WAITING_CONT state if not, or the COMMAND_RUN state if you do.

    The INPUT_WAIT_CONT state would printout the dquote> prompt, and take similar actions... 'do we have enough information?' Yes: COMMAND_RUN, No: INPUT_WAIT_CONT.

    It's then up to you to revert to the INPUT_WAIT state and redraw the mysh$ prompt when the user presses ^C, or when the command execution has completed.