How to capture Control+D signal?

92,676

Solution 1

As others have already said, to handle Control+D, handle "end of file"s.

Control+D is a piece of communication between the user and the pseudo-file that you see as stdin. It does not mean specifically "end of file", but more generally "flush the input I typed so far". Flushing means that any read() call on stdin in your program returns with the length of the input typed since the last flush. If the line is nonempty, the input becomes available to your program although the user did not type "return" yet. If the line is empty, then read() returns with zero, and that is interpreted as "end of file".

So when using Control+D to end a program, it only works at the beginning of a line, or if you do it twice (first time to flush, second time for read() to return zero).

Try it:

$ cat
foo
   (type Control-D once)
foofoo (read has returned "foo")
   (type Control-D again)
$

Solution 2

Ctrl+D is not a signal, it's EOF (End-Of-File). It closes the stdin pipe. If read(STDIN) returns 0, it means stdin closed, which means Ctrl+D was hit (assuming there is a keyboard at the other end of the pipe).

Solution 3

A minimalistic example:

#include <unistd.h> 
#include <stdio.h> 
#include <termios.h> 
#include <signal.h> 

void sig_hnd(int sig){ (void)sig; printf("(VINTR)"); }

int main(){
  setvbuf(stdout,NULL,_IONBF,0);

  struct termios old_termios, new_termios;
  tcgetattr(0,&old_termios);

  signal( SIGINT, sig_hnd );

  new_termios             = old_termios;
  new_termios.c_cc[VEOF]  = 3; // ^C
  new_termios.c_cc[VINTR] = 4; // ^D
  tcsetattr(0,TCSANOW,&new_termios);

  char line[256]; int len;
  do{
    len=read(0,line,256); line[len]='\0';
    if( len <0 ) printf("(len: %i)",len);
    if( len==0 ) printf("(VEOF)");
    if( len >0 ){
      if( line[len-1] == 10 ) printf("(line:'%.*s')\n",len-1,line);
      if( line[len-1] != 10 ) printf("(partial line:'%s')",line);
    }
  }while( line[0] != 'q' );

  tcsetattr(0,TCSANOW,&old_termios);
}

The program change the VEOF char (from Ctrl-D) to Ctrl-C and the VINTR char (from Ctrl-C) to Ctrl-D. If You press Ctrl-D then the terminal driver will send a SIGINT to the signal handler of the program.

Note: pressing VINTR will erase the terminal input buffer so You can not read the characters typed in the line before the VINTR key pressed.

Solution 4

There's no need to process signals.

You need to ensure ISIG is not set on the terminal flags, that's all.

Here's a complete contained example using select to avoid blocking on stdin:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <time.h>
#include <sys/select.h>

#define STDIN_FILENO 0

struct termios org_opts;

/** Select to check if stdin has pending input */
int pending_input(void) {
  struct timeval tv;
  fd_set fds;
  tv.tv_sec = 0;
  tv.tv_usec = 0;
  FD_ZERO(&fds);
  FD_SET(STDIN_FILENO, &fds); //STDIN_FILENO is 0
  select(STDIN_FILENO+1, &fds, NULL, NULL, &tv);
  return FD_ISSET(STDIN_FILENO, &fds);
}

/** Input terminal mode; save old, setup new */
void setup_terminal(void) {
  struct termios new_opts;
  tcgetattr(STDIN_FILENO, &org_opts);
  memcpy(&new_opts, &org_opts, sizeof(new_opts));
  new_opts.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHOK | ECHONL | ECHOPRT | ECHOKE | ISIG | ICRNL);
  tcsetattr(STDIN_FILENO, TCSANOW, &new_opts);
}

/** Shutdown terminal mode */
void reset_terminal(void) {
  tcsetattr(STDIN_FILENO, TCSANOW, &org_opts);
}

/** Return next input or -1 if none */
int next_input(void) {
  if (!pending_input())
    return -1;
  int rtn = fgetc(stdin);
  printf("Found: %d\n", rtn);
  return(rtn);
}

int main()
{
  setup_terminal();

  printf("Press Q to quit...\n");
  for (;;) {
    int key = next_input();
    if (key != -1) {
      if ((key == 113) || (key == 81)) {
        printf("\nNormal exit\n");
        break;
      }
    }
  }

  reset_terminal();
  return 0;
}

Output:

doug-2:rust-sys-sterm doug$ cc junk.c
doug-2:rust-sys-sterm doug$ ./a.out
Press Q to quit...
Found: 4
Found: 3
Found: 27
Found: 26
Found: 113

Normal exit

NB. 3 is control C and 4 is control D; 26 is control z. 113 is 'q'. See: http://en.wikipedia.org/wiki/ASCII#ASCII_control_characters for a full table.

Solution 5

As far as I know Ctrl+D is translated by the system to end of standard input so your app won't get any signal.

I think that the only way to intercept Ctrl+D is to work directly with the system api (like accessing tty)

Share:
92,676
Admin
Author by

Admin

Updated on July 05, 2022

Comments

  • Admin
    Admin almost 2 years

    I want to capture the Ctrl+D signal in my program and write a signal handler for it. How can I do that? I am working on C and using a Linux system.

  • sambowry
    sambowry over 14 years
    VEOF (^D) does not transformed to be an EOF. If You press VEOF the program will receive a partial buffer as Pascal Cuoq wrote.
  • Jens
    Jens over 11 years
    Your main paramter list needs a void. This is not C++.
  • Stephane Chazelas
    Stephane Chazelas over 10 years
    Talking of pipe here is misleading. CTRL-D is only relevant for terminal devices, not pipes, and it's only relevant on the master side of the pseudo-terminal or when sent by the (real) terminal, and only when in icanon mode.
  • Doug
    Doug over 9 years
    Hey, for anyone else who finds this question; please stop up voting this solution. It may be 'technically correct' in that control-D is not a signal, but for programmers, it's the most useless answer on this whole page.
  • Pascal Cuoq
    Pascal Cuoq over 9 years
    @Doug Most programmers who want to handle control-D simply want to handle EOF. I don't see any indication in the question that the OP wants to be able to read control-D as an input character. The answer could be more complete by including a specific way to handle control-D, but the OP did not provide any code, so it is hard to make any modification suggestion other than looking at stackoverflow.com/search?q=%5Bc%5D+eof . Perhaps your answer would be a better fit for a question that specifically asks how to treat control-D as an ordinary input character.
  • Doug
    Doug over 9 years
    The issue here is that this answer doesn't answer the question. 'Just handle EOF'. HOW? How do you keep your application from being terminated and read from stdin? Do you reopen stdin somehow? 'Just do this thing, which actually I'm not going to tell you how to do' is an extremely unhelpful answer.
  • Pascal Cuoq
    Pascal Cuoq over 9 years
    @Doug This second comment makes it clearer what you mean than your first comment, which is more of a patronizing call to evaluate the answer the same way you evaluate it with no justification provided.
  • Ionoclast Brigham
    Ionoclast Brigham about 9 years
    Technically, this is C++. g++ -std=c++03 test.cpp compiles and runs just fine :)
  • Jostikas
    Jostikas over 7 years
    Just to get cute with you: But the OP asked about C... :)
  • Enlico
    Enlico about 4 years
    @PascalCuoq, after writing cat hitting enter, and then writing foo, I have to press ctrl-D twice to get a foo "appended" to my foo, thus becoming a foofoo. The third time I press ctrl-D, I'm back to the terminal. Can you please review it yourself?
  • Pascal Cuoq
    Pascal Cuoq about 4 years
    @EnricoMariaDeAngelis For me it is still working exactly the way it was when I wrote this answer: First Ctrl-D causes “foofoo” to be shown, and second Ctrl-D takes me back to the terminal. I have never encountered a terminal that worked differently (on OS X and Linux), but perhaps your terminal is withholding the first Ctrl-D for some reason?
  • numzero
    numzero about 4 years
    @EnricoMariaDeAngelis On my system (Void Linux /musl), cat works as described, but gcc (gcc --language=c++ /dev/stdin) requires these three ^D to get EOF. Maybe some buffering layer uses read to recheck for EOF after some lower layer signalled it?
  • Enlico
    Enlico about 4 years
    @PascalCuoq, I frequently forget that I have alias cat='bat -pp' in my .bashrc. False alarm. command cat works as expected.
  • Fayeure
    Fayeure almost 3 years
    What does the TCSANOW do in your tcsetattr?