How to stop control-d from exiting shell in c - c

I am writing my own shell and I want to stop the user from exiting the shell with CTRL+D. Below is how I am checking for EOF and I am able to catch the CTRL+D:
However, my loop goes into infinite and does prints please use _exit over and over again.
How could I stop this from doing so?
Thanks

OK, this seems to work:
#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
#include <unistd.h>
void disable_veof(void) {
struct termios t;
int r;
r = tcgetattr(STDIN_FILENO, &t);
if(r)
exit(EXIT_FAILURE);
t.c_cc[VEOF] = _POSIX_VDISABLE;
r = tcsetattr(STDIN_FILENO, TCSANOW, &t);
if(r)
exit(EXIT_FAILURE);
}
void echo_lines(void) {
char buffer[4096];
const size_t buffer_len = sizeof buffer;
ssize_t bytes;
while( 0 != (bytes = read(STDIN_FILENO, buffer, buffer_len)) ) {
bytes = write(STDOUT_FILENO, buffer, bytes);
if(bytes <= 0)
exit(EXIT_FAILURE);
}
}
int main() {
disable_veof();
echo_lines();
return EXIT_SUCCESS;
}

If the input is a terminal in canonical mode, EOF in raw io (as opposed to stdio where it is) is not sticky.
https://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap11.html#tag_11_01_09
Special character on input, which is recognized if the ICANON flag is
set. When received, all the bytes waiting to be read are immediately
passed to the process without waiting for a newline, and the EOF is
discarded. Thus, if there are no bytes waiting (that is, the EOF
occurred at the beginning of a line), a byte count of zero shall be
returned from the read(), representing an end-of-file indication. If
ICANON is set, the EOF character shall be discarded when processed.
The next read() should return a positive value unless the user keeps pressing Ctrl+D.

Related

Exit a program using a specific keypress combination? [duplicate]

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.
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)
$
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).
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.
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.
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)
You can use poll() and watch for POLLHUP on fd #1, because the TTY layer translates ^D to EOF.
Ctrl + D value in ascci table is 4 and is a non printable characters. So your can capture it in a terminal with the following code.
When getline function get Ctrl + D an error occur and return value is -1.
Your can make a condition on the return value.
#include <stdio.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char *buf = malloc(sizeof(char) * 500);
size_t size = 500;
int nb = getline(&buf, &size, stdin);
if (nb == -1)
printf("CTRL + D captured\n");
free(buf);
return (0);
}
You can check if stdin is not of with the feof method like so:
if (feof(stdin))
{
// some code
exit(0);
}
See this for more details/
According to the man page, getline() will return -1 on failure to read a line (including the end-of-file condition).
This means:
When getline() encounters a failure it will return -1
When getline() reads CTRL+D it will return -1
Since getline() returns a value of type ssize_t which is defined as a signed integer value, you can construct your code in such a way to test for the -1 value which will be returned in the event of an end-of-file condition or failure to read a line.
#include <stdio.h>
#include <stdlib>
int main(void)
{
char *buffer = NULL;
size_t bufsize = 0;
ssize_t characters;
characters = getline(&buffer, &bufsize, stdin);
if (characters == -1)
{
printf("You entered CTRL+D\n");
}
free(buffer);
return (0);
}

How to flush EOF and enable scanf to get new input without starting over the program in C [duplicate]

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.
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)
$
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).
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.
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.
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)
You can use poll() and watch for POLLHUP on fd #1, because the TTY layer translates ^D to EOF.
Ctrl + D value in ascci table is 4 and is a non printable characters. So your can capture it in a terminal with the following code.
When getline function get Ctrl + D an error occur and return value is -1.
Your can make a condition on the return value.
#include <stdio.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char *buf = malloc(sizeof(char) * 500);
size_t size = 500;
int nb = getline(&buf, &size, stdin);
if (nb == -1)
printf("CTRL + D captured\n");
free(buf);
return (0);
}
You can check if stdin is not of with the feof method like so:
if (feof(stdin))
{
// some code
exit(0);
}
See this for more details/
According to the man page, getline() will return -1 on failure to read a line (including the end-of-file condition).
This means:
When getline() encounters a failure it will return -1
When getline() reads CTRL+D it will return -1
Since getline() returns a value of type ssize_t which is defined as a signed integer value, you can construct your code in such a way to test for the -1 value which will be returned in the event of an end-of-file condition or failure to read a line.
#include <stdio.h>
#include <stdlib>
int main(void)
{
char *buffer = NULL;
size_t bufsize = 0;
ssize_t characters;
characters = getline(&buffer, &bufsize, stdin);
if (characters == -1)
{
printf("You entered CTRL+D\n");
}
free(buffer);
return (0);
}

How to stop Ctrl-D from exiting in my own shell [duplicate]

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.
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)
$
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).
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.
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.
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)
You can use poll() and watch for POLLHUP on fd #1, because the TTY layer translates ^D to EOF.
Ctrl + D value in ascci table is 4 and is a non printable characters. So your can capture it in a terminal with the following code.
When getline function get Ctrl + D an error occur and return value is -1.
Your can make a condition on the return value.
#include <stdio.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char *buf = malloc(sizeof(char) * 500);
size_t size = 500;
int nb = getline(&buf, &size, stdin);
if (nb == -1)
printf("CTRL + D captured\n");
free(buf);
return (0);
}
You can check if stdin is not of with the feof method like so:
if (feof(stdin))
{
// some code
exit(0);
}
See this for more details/
According to the man page, getline() will return -1 on failure to read a line (including the end-of-file condition).
This means:
When getline() encounters a failure it will return -1
When getline() reads CTRL+D it will return -1
Since getline() returns a value of type ssize_t which is defined as a signed integer value, you can construct your code in such a way to test for the -1 value which will be returned in the event of an end-of-file condition or failure to read a line.
#include <stdio.h>
#include <stdlib>
int main(void)
{
char *buffer = NULL;
size_t bufsize = 0;
ssize_t characters;
characters = getline(&buffer, &bufsize, stdin);
if (characters == -1)
{
printf("You entered CTRL+D\n");
}
free(buffer);
return (0);
}

Can I make ungetc unblock a blocking fgetc call?

I would like to stuff an 'A' character back into stdin using ungetc on receipt of SIGUSR1. Imagine that I have a good reason for doing this.
When calling foo(), the blocking read in stdin is not interrupted by the ungetc call on receipt of the signal. While I didn't expect this to work as is, I wonder if there is a way to achieve this - does anyone have suggestions?
void handler (int sig)
{
ungetc ('A', stdin);
}
void foo ()
{
signal (SIGUSR1, handler);
while ((key = fgetc (stdin)) != EOF)
{
...
}
}
Rather than try to get ungetc() to unblock a blocking fgetc() call via a signal, perhaps you could try not having fgetc() block to begin with and wait for activity on stdin using select().
By default, the line discipline for a terminal device may work in canonical mode. In this mode, the terminal driver doesn't present the buffer to userspace until the newline is seen (Enter key is pressed).
To accomplish what you want, you can set the terminal into raw (non-canonical) mode by using tcsetattr() to manipulate the termios structure. This should case the blocking call to fgetc() to immediately return the character inserted with ungetc().
void handler(int sig) {
/* I know I shouldn't do this in a signal handler,
* but this is modeled after the OP's code.
*/
ungetc('A', stdin);
}
void wait_for_stdin() {
fd_set fdset;
FD_ZERO(&fdset);
FD_SET(fileno(stdin),&fdset);
select(1, &fdset, NULL, NULL, NULL);
}
void foo () {
int key;
struct termios terminal_settings;
signal(SIGUSR1, handler);
/* set the terminal to raw mode */
tcgetattr(fileno(stdin), &terminal_settings);
terminal_settings.c_lflag &= ~(ECHO|ICANON);
terminal_settings.c_cc[VTIME] = 0;
terminal_settings.c_cc[VMIN] = 0;
tcsetattr(fileno(stdin), TCSANOW, &terminal_settings);
for (;;) {
wait_for_stdin();
key = fgetc(stdin);
/* terminate loop on Ctrl-D */
if (key == 0x04) {
break;
}
if (key != EOF) {
printf("%c\n", key);
}
}
}
NOTE: This code omits error checking for simplicity.
Clearing the ECHO and ICANON flags respectively disables echoing of characters as they are typed and causes read requests to be satisfied directly from the input queue. Setting the values of VTIME and VMIN to zero in the c_cc array causes the read request (fgetc()) to return immediately rather than block; effectively polling stdin. This causes key to get set to EOF so another method for terminating the loop is necessary. Unnecessary polling of stdin is reduced by waiting for activity on stdin using select().
Executing the program, sending a SIGUSR1 signal, and typing
t e s t results in the following output1:
A
t
e
s
t
1) tested on Linux
It is not entirely clear what your goal is, but is this what you are looking for?
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
int handle = 0;
void handler (int sig) {
handle = 1;
}
void foo () {
int key;
signal (SIGUSR1, handler);
while ((key = fgetc (stdin)) != EOF) {
printf("%c\n",key);
if (handle) {
handle = 0;
ungetc('A',stdin);
}
}
}
int main(void) {
printf("PID: %d\n",getpid());
foo();
}
It produces this output:
PID: 8902
test (typed on stdin)
t
A
e
s
t
FILE*s are not async safe.
You cannot operate on a FILE* in a signal handler while someone else also uses that same FILE*. functions you can all in a signal handler is stated here:
http://www.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_04.html . (It might be
different on a windows machine, but still any FILE* are not safe there either.
This is essentially the same as #Jamie's answer, slightly changed to support your desire to process the A before the t, but it's too hard to type code into a comment box, so I've posted this answer separately.
int insert_an_A = 0;
void handler(int sig) { insert_an_A = 1; }
void process_char(char c) { ... }
int main(int argc, char **argv) {
int c;
/* skip signal setup */
while ((c = fgetc(stdin)) != EOF) {
if (insert_an_A) {
process_char('A');
insert_an_A = 0;
}
process_char(c);
}
}
If you want to process an handler received during the call to fgetc that returns EOF, you should also check insert_an_A after exiting the while loop.
Note also that in general the best practice for signal handlers is to set a global variable and return from the handler. Elsewhere in your program, look for that variable changing and react appropriately.

How to capture Control+D signal?

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.
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)
$
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).
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.
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.
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)
You can use poll() and watch for POLLHUP on fd #1, because the TTY layer translates ^D to EOF.
Ctrl + D value in ascci table is 4 and is a non printable characters. So your can capture it in a terminal with the following code.
When getline function get Ctrl + D an error occur and return value is -1.
Your can make a condition on the return value.
#include <stdio.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char *buf = malloc(sizeof(char) * 500);
size_t size = 500;
int nb = getline(&buf, &size, stdin);
if (nb == -1)
printf("CTRL + D captured\n");
free(buf);
return (0);
}
You can check if stdin is not of with the feof method like so:
if (feof(stdin))
{
// some code
exit(0);
}
See this for more details/
According to the man page, getline() will return -1 on failure to read a line (including the end-of-file condition).
This means:
When getline() encounters a failure it will return -1
When getline() reads CTRL+D it will return -1
Since getline() returns a value of type ssize_t which is defined as a signed integer value, you can construct your code in such a way to test for the -1 value which will be returned in the event of an end-of-file condition or failure to read a line.
#include <stdio.h>
#include <stdlib>
int main(void)
{
char *buffer = NULL;
size_t bufsize = 0;
ssize_t characters;
characters = getline(&buffer, &bufsize, stdin);
if (characters == -1)
{
printf("You entered CTRL+D\n");
}
free(buffer);
return (0);
}

Resources