Partial write due to SIGINT - c

I have a write_full which should write the buffer fully to standard output even if it was interrupted by a signal.
I have a loop which keep write_full a string until quit is changed by a signal handler. Here's the code:
#include <signal.h>
#include <unistd.h>
#include <errno.h>
volatile sig_atomic_t quit = 0;
void sigint_handler(int s)
{
quit = 1;
}
int write_full(char *buf, size_t len)
{
while (len > 0) {
ssize_t written = write(STDOUT_FILENO, buf, len);
if (written == -1) {
if (errno == EINTR) {
continue;
}
return -1;
}
buf += written;
len -= (size_t)written;
}
return 0;
}
int main(void)
{
struct sigaction act = {
.sa_handler = sigint_handler
};
sigaction(SIGINT, &act, NULL);
while (!quit) {
write_full("loop\n", 5);
}
write_full("cleanup", 7);
return 0;
}
I expect the program to write the "loop" fully before printing "cleanup", but I see an output like this:
loop
loop
loop
l^C
cleanup
Why is this happening? I expect it to be something like this:
loop
loop
loop
l^Coop
cleanup
because the write_full should continue writing the "oop\n" part even after the first write was a short write due to interrupt. I put a breakpoint at the signal handler and stepped and it seems like write is reporting that it has written 4 characters even when it only wrote "l" to the stdout. So instead of writing "oop\n" next, it only writes "\n".
l^C
Program received signal SIGINT, Interrupt.
Breakpoint 1, sigint_handler (s=2) at src/main.c:9
9 quit = 1;
(gdb) next
10 }
(gdb) next
write_full (buf=0x4020a0 "loop\n", len=5) at src/main.c:16
16 if (written == -1) {
(gdb) print written
$1 = 4
Why is this happening? And how can I fix this?

Ctrl-C messes up terminal output. The program writes all it should write, but by default the terminal driver cuts the line after Ctrl-C. This is not under control of your program. The cut happens if the driver sees Ctrl-C in the middle of copying of the complete line buffer to the physical device. Input line buffer is also discarded. This pertains to other signal-generating characters as well.
This is described rather briefly in the stty(1) and termios(3) manual pages.
This behaviour can be disabled with stty noflsh command.
You can also redirect to a file to see the complete output.

Related

How to remove characters(^\), ctrl + \(SIGQUIT), from STDIN in C [duplicate]

Good day,
I'm writing my own shell in C for my school which has to resemble bash as closely as possible.
I have to handle signals such as Ctrl-\ and Ctrl-C as bash does; for this reason I'm allowed to use signal function. It works fine, but the thing is whenever a Ctrl-C signal is caught (starting from the second catch), a ^C is printed.
On the net, I've found a workaround suggesting printing "\b \b\b \b\nminishell$ " whenever a Ctrl-C is caught, which will devour the two symbols. The thing is, since at the very first time ^C is not printed, the print devours two symbols of my prompting, making it just minishell instead of minishell$ , with the cursor incorrectly displayed.
Now I've come up with another workaround for this workaround which is to declare a static boolean to not print the baskspaces at the very first call. This doesn't help in case of Ctrl-\ though; Ctrl-\ proceeds to move my cursor to right when I attempt to write the two whitespaces that must replace the ^\.
I don't like these workarounds and would like to know whether there is a way to instruct the terminal not to output this stuff? I'm allowed to use tgetent, tgetflag, tgetnum, tgetstr, tgoto, tputs, tcsetattr, tcgetattr, have read their man pages but nothing seems to be helpful.
When you type a key on a terminal, two things happen
the character is echoed (displayed) on this terminal
the character is sent (over the line) to the attached program
Both these actions can be controlled via termios/tcsetattr(): a different character(s) can be sent or echoed, some can be suppressed, etc. (some/most of these actions take place in the terminal-driver , but this is not relevant here)
Demonstration: using tcsetattr() to control the echoing of the terminal:
#include <stdio.h>
#include <stdlib.h>
#define _SVID_SOURCE 1
#include <termios.h>
#include <unistd.h>
#include <signal.h>
struct termios termios_save;
void reset_the_terminal(void)
{
tcsetattr(0, 0, &termios_save );
}
sig_atomic_t the_flag = 0;
void handle_the_stuff(int num)
{
char buff[4];
buff[0] = '[';
buff[2] = '0' + num%10;
num /= 10;
buff[1] = '0' + num%10;
buff[3] = ']';
write(0, buff, sizeof buff);
the_flag = 1;
}
int main (void)
{
int rc;
int ch;
struct termios termios_new;
rc = tcgetattr(0, &termios_save );
if (rc) {perror("tcgetattr"); exit(1); }
rc = atexit(reset_the_terminal);
if (rc) {perror("atexit"); exit(1); }
termios_new = termios_save;
termios_new.c_lflag &= ~ECHOCTL;
rc = tcsetattr(0, 0, &termios_new );
if (rc) {perror("tcsetattr"); exit(1); }
signal(SIGINT, handle_the_stuff);
printf("(pseudoshell)Start typing:\n" );
while(1) {
ch = getc(stdin);
if (the_flag) {
printf("Saw the signal, last character was %02x\n", (unsigned) ch);
break;
}
}
exit (0);
}
The way to set the console such a SW may intercept all typed chars is to set the terminal in RAW MODE. The problems this way may present are that all keys that aren't in the ASCII 0-255 space, such as è, ì, à will be received from the console as a bytes sequence and all the function and control keys included cursors and backspace will not accomplish any action, some code such as CR, LF and some ANSI sequence may accomplish actions when are read from the input channel and rewritten on the output channel.
To set the terminal in raw mode you have to use the function cfmakeraw followed by the function tcsetattr.
The code below implements a simple but not very good implemented terminal, anyway I think this code is a good point to start. In any case, the code flow and the error control must be at least better arranged.
The code writes all sequence of ASCII char that enter into the console when a key is typed. All chars that have value smaller then 32 or greater then 126 will be written as [HEX-CODE]
I.E. hitting Esc on the console will be written [1B], the code of Ctrl+C will be written as [03], F1 will be [1B]OP, F11 will be [1B][23~, Enter will be [0D].
If you will hit Ctrl+X [18] will be written and the program stops, but this behaviour is under SW control as you can see in the code.
Here the code:
#include <stdio.h> // Standard input/output definitions
#include <string.h> // String function definitions
#include <unistd.h> // UNIX standard function definitions
#include <fcntl.h> // File control definitions
#include <errno.h> // Error number definitions
#include <termios.h> // POSIX terminal control definitions (struct termios)
#include <sys/ioctl.h> // Used for TCGETS2, which is required for custom baud rates
#include <sys/select.h> // might be used to manage select
int setAttr(int ch, int resetToOld);
#define IN 0
#define OUT 1
typedef struct TermCap
{
int fd;
struct termios oldTermios;
struct termios newTermios;
// fd_set fds; // might be used to manage select
} TermCap;
TermCap m_termCap[2];
int main()
{
int i,ex=0;
char msg;
char buff[20];
m_termCap[IN].fd=STDIN_FILENO;
m_termCap[OUT].fd=STDOUT_FILENO;
// Gets STDIN config and set raw config
setAttr(IN,0);
// Gets STDOUT config and set raw config
setAttr(OUT,0);
// Console loop ... the console terminates when ^X is intercepted.
do {
do {
i=read(m_termCap[IN].fd,&msg,1);
if (i>0){
if (msg<32 || msg>126) {
sprintf(buff,"[%02X]",(unsigned char)msg);
write(m_termCap[OUT].fd,buff,4);
if (msg==24)
ex=1;
}else{
write(m_termCap[OUT].fd,&msg,i);
}
}
usleep(10000); // a minimal delay of 10 millisec
} while(i>0 && !ex);
} while(!ex);
// Reset console to initial state.
setAttr(IN,1);
setAttr(OUT,1);
printf("\r\n\nThe end!");
return 0;
}
int setAttr(int ch, int resetToOld)
{
int retVal=0;
int i;
if (!resetToOld) {
// Read old term config
i=tcgetattr(m_termCap[ch].fd, &m_termCap[ch].oldTermios);
if (i==-1) {
return 1;
}
}
m_termCap[ch].newTermios = m_termCap[ch].oldTermios;
if (!resetToOld) {
// Terminal in raw mode
cfmakeraw(&m_termCap[ch].newTermios);
}
i=tcsetattr(m_termCap[ch].fd, TCSANOW, &m_termCap[ch].newTermios);
if (i==-1) {
retVal = 2;
}
return retVal;
}
Wouldn't this work?
void signalHandler(int signo){
if(signo==SIGINT){
printf("\b\b \b\b");
fflush(NULL);
printf("\nHello World\n");
}
}
In my shell it seems to work fine. The first printf and fflush is what you have to implement in your handler. The printf after that is just a way for me to show you that you can, then, do whatever you want after the ^C not appearing.
Why does this make it not appear? In the first printf I erase the characters by using backspaces and spaces. As stdout is buffered by default and I didn't want to use a newline character, I flushed the buffer manually.

Forcing a terminal not to print Ctrl hotkeys when signals are caught

Good day,
I'm writing my own shell in C for my school which has to resemble bash as closely as possible.
I have to handle signals such as Ctrl-\ and Ctrl-C as bash does; for this reason I'm allowed to use signal function. It works fine, but the thing is whenever a Ctrl-C signal is caught (starting from the second catch), a ^C is printed.
On the net, I've found a workaround suggesting printing "\b \b\b \b\nminishell$ " whenever a Ctrl-C is caught, which will devour the two symbols. The thing is, since at the very first time ^C is not printed, the print devours two symbols of my prompting, making it just minishell instead of minishell$ , with the cursor incorrectly displayed.
Now I've come up with another workaround for this workaround which is to declare a static boolean to not print the baskspaces at the very first call. This doesn't help in case of Ctrl-\ though; Ctrl-\ proceeds to move my cursor to right when I attempt to write the two whitespaces that must replace the ^\.
I don't like these workarounds and would like to know whether there is a way to instruct the terminal not to output this stuff? I'm allowed to use tgetent, tgetflag, tgetnum, tgetstr, tgoto, tputs, tcsetattr, tcgetattr, have read their man pages but nothing seems to be helpful.
When you type a key on a terminal, two things happen
the character is echoed (displayed) on this terminal
the character is sent (over the line) to the attached program
Both these actions can be controlled via termios/tcsetattr(): a different character(s) can be sent or echoed, some can be suppressed, etc. (some/most of these actions take place in the terminal-driver , but this is not relevant here)
Demonstration: using tcsetattr() to control the echoing of the terminal:
#include <stdio.h>
#include <stdlib.h>
#define _SVID_SOURCE 1
#include <termios.h>
#include <unistd.h>
#include <signal.h>
struct termios termios_save;
void reset_the_terminal(void)
{
tcsetattr(0, 0, &termios_save );
}
sig_atomic_t the_flag = 0;
void handle_the_stuff(int num)
{
char buff[4];
buff[0] = '[';
buff[2] = '0' + num%10;
num /= 10;
buff[1] = '0' + num%10;
buff[3] = ']';
write(0, buff, sizeof buff);
the_flag = 1;
}
int main (void)
{
int rc;
int ch;
struct termios termios_new;
rc = tcgetattr(0, &termios_save );
if (rc) {perror("tcgetattr"); exit(1); }
rc = atexit(reset_the_terminal);
if (rc) {perror("atexit"); exit(1); }
termios_new = termios_save;
termios_new.c_lflag &= ~ECHOCTL;
rc = tcsetattr(0, 0, &termios_new );
if (rc) {perror("tcsetattr"); exit(1); }
signal(SIGINT, handle_the_stuff);
printf("(pseudoshell)Start typing:\n" );
while(1) {
ch = getc(stdin);
if (the_flag) {
printf("Saw the signal, last character was %02x\n", (unsigned) ch);
break;
}
}
exit (0);
}
The way to set the console such a SW may intercept all typed chars is to set the terminal in RAW MODE. The problems this way may present are that all keys that aren't in the ASCII 0-255 space, such as è, ì, à will be received from the console as a bytes sequence and all the function and control keys included cursors and backspace will not accomplish any action, some code such as CR, LF and some ANSI sequence may accomplish actions when are read from the input channel and rewritten on the output channel.
To set the terminal in raw mode you have to use the function cfmakeraw followed by the function tcsetattr.
The code below implements a simple but not very good implemented terminal, anyway I think this code is a good point to start. In any case, the code flow and the error control must be at least better arranged.
The code writes all sequence of ASCII char that enter into the console when a key is typed. All chars that have value smaller then 32 or greater then 126 will be written as [HEX-CODE]
I.E. hitting Esc on the console will be written [1B], the code of Ctrl+C will be written as [03], F1 will be [1B]OP, F11 will be [1B][23~, Enter will be [0D].
If you will hit Ctrl+X [18] will be written and the program stops, but this behaviour is under SW control as you can see in the code.
Here the code:
#include <stdio.h> // Standard input/output definitions
#include <string.h> // String function definitions
#include <unistd.h> // UNIX standard function definitions
#include <fcntl.h> // File control definitions
#include <errno.h> // Error number definitions
#include <termios.h> // POSIX terminal control definitions (struct termios)
#include <sys/ioctl.h> // Used for TCGETS2, which is required for custom baud rates
#include <sys/select.h> // might be used to manage select
int setAttr(int ch, int resetToOld);
#define IN 0
#define OUT 1
typedef struct TermCap
{
int fd;
struct termios oldTermios;
struct termios newTermios;
// fd_set fds; // might be used to manage select
} TermCap;
TermCap m_termCap[2];
int main()
{
int i,ex=0;
char msg;
char buff[20];
m_termCap[IN].fd=STDIN_FILENO;
m_termCap[OUT].fd=STDOUT_FILENO;
// Gets STDIN config and set raw config
setAttr(IN,0);
// Gets STDOUT config and set raw config
setAttr(OUT,0);
// Console loop ... the console terminates when ^X is intercepted.
do {
do {
i=read(m_termCap[IN].fd,&msg,1);
if (i>0){
if (msg<32 || msg>126) {
sprintf(buff,"[%02X]",(unsigned char)msg);
write(m_termCap[OUT].fd,buff,4);
if (msg==24)
ex=1;
}else{
write(m_termCap[OUT].fd,&msg,i);
}
}
usleep(10000); // a minimal delay of 10 millisec
} while(i>0 && !ex);
} while(!ex);
// Reset console to initial state.
setAttr(IN,1);
setAttr(OUT,1);
printf("\r\n\nThe end!");
return 0;
}
int setAttr(int ch, int resetToOld)
{
int retVal=0;
int i;
if (!resetToOld) {
// Read old term config
i=tcgetattr(m_termCap[ch].fd, &m_termCap[ch].oldTermios);
if (i==-1) {
return 1;
}
}
m_termCap[ch].newTermios = m_termCap[ch].oldTermios;
if (!resetToOld) {
// Terminal in raw mode
cfmakeraw(&m_termCap[ch].newTermios);
}
i=tcsetattr(m_termCap[ch].fd, TCSANOW, &m_termCap[ch].newTermios);
if (i==-1) {
retVal = 2;
}
return retVal;
}
Wouldn't this work?
void signalHandler(int signo){
if(signo==SIGINT){
printf("\b\b \b\b");
fflush(NULL);
printf("\nHello World\n");
}
}
In my shell it seems to work fine. The first printf and fflush is what you have to implement in your handler. The printf after that is just a way for me to show you that you can, then, do whatever you want after the ^C not appearing.
Why does this make it not appear? In the first printf I erase the characters by using backspaces and spaces. As stdout is buffered by default and I didn't want to use a newline character, I flushed the buffer manually.

read on a pipe blocks until program running at end of pipe terminates - Windows

I have a sample program that outputs a line of text every second. In the test program below, this program writes some text to stdout then waits 1 second and repeats 20 times.
I have another program which uses popen (_popen on Windows) to open a pipe for reading from the program. I then use fgets to read data. The problem I have is that the fgets blocks until the program terminates. Then I get all the output, all 20 lines, in one go. I want to get the output a line at a time, then ok for fgets to block until next line ready. The reason is I plan to use this on a program that will be constantly running, outputting text, e.g. like the use of tail.
If I run this code example on a program that outputs some text all in one go and exits then it works fine.
Why does fgets block? The test program does print some text immediately, so why doesn't fgets read this first line of text immediately?
Here is the code:
#include <stdio.h>
#include <windows.h>
void execute(const char* cmd) {
char buffer[128] = { 0 };
FILE* pipe = _popen(cmd, "r");
if (!pipe) {
printf("popen() failed!\n");
return;
}
while (!feof(pipe)) {
if (fgets(buffer, 128, pipe) != nullptr)
printf("%s", buffer);
}
int rc = _pclose(pipe);
if (rc != EXIT_SUCCESS) { // return code not 0
printf("pclose exit failure: %d\n", rc);
}
}
int main(int argc, char* argv[]) {
if (argc != 2) {
printf("Usage: pipe_test.exe <program>\n");
exit(1);
}
execute(argv[1]);
}
The program run, helloworld.exe:
#include <stdio.h>
#include <windows.h>
int main() {
for (int i = 0; i < 20; i++) {
printf("Hello World %d\n", i);
Sleep(1000);
}
}
Why does fgets block?
Because it's waiting for the children to output something.
The test program does print some text immediately, so why doesn't fgets read this first line of text immediately?
It actually does not print text immediately. The problem here, as #Barmar notices, is that writing to a pipe is buffered (and not line buffered) by the C standard library implementation. This buffering happens in your child program (helloworld), not in your parent program (pipe_test).
From your parent program, you have no control over what the children spawned through popen() will do, therefore if the child output is buffered like in this case, the only thing you can do (without modifying the child's code) is to wait until the buffer is flushed to the pipe.
In order to get the output sooner, you would have to modify the children's code to manually call fflush() or use setvbuf() to disable buffering:
int main() {
setvbuf(stdout, NULL, _IONBF, 0); // Disable buffering on stdout.
for (int i = 0; i < 20; i++) {
printf("Hello World %d\n", i);
Sleep(1000);
}
}
There's really not much else you can do.

C Stop stdout from flushing

I am writing a C program where I am printing to stderr and also using putchar() within the code. I want the output on the console to show all of the stderr and then finally flush the stdout before the program ends. Does anyone know of a method that will stop stdout from flushing when a putchar('\n') occurs?
I suppose i could just do an if statement to make sure it doesn't putchar any newlines but I would prefer some line or lines of code to put at the top of the program to stop all flushing until i say fflush(stdout) at the bottom of the program
What you're trying to do is horribly fragile. C provides no obligation for an implementation of stdio not to flush output, under any circumstances. Even if you get it to work for you, this behavior will be dependent on not exceeding the buffer size. If you really need this behavior, you should probably buffer the output yourself (possibly writing it to a tmpfile() rather than stdout) then copying it all to stdout as the final step before your program exits.
Run your command from the console using pipeling:
my_command >output.txt
All output witten to stderr will appear immediately. The stuff written to stdout will go to output.txt.
Windows only. I'm still looking for the Unix solution myself if anyone has it!
Here is a minimal working example for Windows that sends a buffer to stdout without flushing. You can adjust the maximum buffer size before a flush occurs by changing max_buffer, though I imagine there's some upper limit!
#include <windows.h>
#include <string.h>
int main()
{
const char* my_buffer = "hello, world!";
HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
int max_buffer = 1000000;
int num_remaining = strlen(my_buffer);
while (num_remaining)
{
DWORD num_written = 0;
int buffer_size = num_remaining < max_buffer ? num_remaining : max_buffer;
int retval = WriteConsoleA(hStdout, my_buffer, buffer_size, &num_written, 0);
if (retval == 0 || num_written == 0)
{
// Handle error
}
num_remaining -= num_written;
if (num_remaining == 0)
{
break;
}
my_buffer += num_written;
}
}
You can use setvbuf() to fully buffer output to stdout and provide a large enough buffer size for your purpose:
#include <stdio.h>
int main() {
// issue this call before any output
setvbuf(stdout, NULL, _IOFBF, 16384);
...
return 0;
}
Output to stderr is unbuffered by default, so it should go to the console immediately.
Output to stdout is line buffered by default when attached to the terminal. Setting it to _IOFBF (fully buffered) should prevent putchar('\n') from flushing the pending output.

How do you do non-blocking console I/O on Linux in C?

How do you do nonblocking console IO on Linux/OS X in C?
I want to add an example:
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
int main(int argc, char const *argv[])
{
char buf[20];
fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_NONBLOCK);
sleep(4);
int numRead = read(0, buf, 4);
if (numRead > 0) {
printf("You said: %s", buf);
}
}
When you run this program you have 4 seconds to provide input to standard in. If no input found, it will not block and will simply return.
2 sample executions:
Korays-MacBook-Pro:~ koraytugay$ ./a.out
fda
You said: fda
Korays-MacBook-Pro:~ koraytugay$ ./a.out
Korays-MacBook-Pro:~ koraytugay$
Like Pete Kirkham, I found cc.byexamples.com, and it worked for me. Go there for a good explanation of the problem, as well as the ncurses version.
My code needed to take an initial command from standard input or a file, then watch for a cancel command while the initial command was processed. My code is C++, but you should be able to use scanf() and the rest where I use the C++ input function getline().
The meat is a function that checks if there is any input available:
#include <unistd.h>
#include <stdio.h>
#include <sys/select.h>
// cc.byexamples.com calls this int kbhit(), to mirror the Windows console
// function of the same name. Otherwise, the code is the same.
bool inputAvailable()
{
struct timeval tv;
fd_set fds;
tv.tv_sec = 0;
tv.tv_usec = 0;
FD_ZERO(&fds);
FD_SET(STDIN_FILENO, &fds);
select(STDIN_FILENO+1, &fds, NULL, NULL, &tv);
return (FD_ISSET(0, &fds));
}
This has to be called before any stdin input function When I used std::cin before using this function, it never returned true again. For example, main() has a loop that looks like this:
int main(int argc, char* argv[])
{
std::string initialCommand;
if (argc > 1) {
// Code to get the initial command from a file
} else {
while (!inputAvailable()) {
std::cout << "Waiting for input (Ctrl-C to cancel)..." << std::endl;
sleep(1);
}
std::getline(std::cin, initialCommand);
}
// Start a thread class instance 'jobThread' to run the command
// Start a thread class instance 'inputThread' to look for further commands
return 0;
}
In the input thread, new commands were added to a queue, which was periodically processed by the jobThread. The inputThread looked a little like this:
THREAD_RETURN inputThread()
{
while( !cancelled() ) {
if (inputAvailable()) {
std::string nextCommand;
getline(std::cin, nextCommand);
commandQueue.lock();
commandQueue.add(nextCommand);
commandQueue.unlock();
} else {
sleep(1);
}
}
return 0;
}
This function probably could have been in main(), but I'm working with an existing codebase, not against it.
For my system, there was no input available until a newline was sent, which was just what I wanted. If you want to read every character when typed, you need to turn off "canonical mode" on stdin. cc.byexamples.com has some suggestions which I haven't tried, but the rest worked, so it should work.
You don't, really. The TTY (console) is a pretty limited device, and you pretty much don't do non-blocking I/O. What you do when you see something that looks like non-blocking I/O, say in a curses/ncurses application, is called raw I/O. In raw I/O, there's no interpretation of the characters, no erase processing etc. Instead, you need to write your own code that checks for data while doing other things.
In modern C programs, you can simplify this another way, by putting the console I/O into a thread or lightweight process. Then the I/O can go on in the usual blocking fashion, but the data can be inserted into a queue to be processed on another thread.
Update
Here's a curses tutorial that covers it more.
I bookmarked "Non-blocking user input in loop without ncurses" earlier this month when I thought I might need non-blocking, non-buffered console input, but I didn't, so can't vouch for whether it works or not. For my use, I didn't care that it didn't get input until the user hit enter, so just used aio to read stdin.
Here's a related question using C++ -- Cross-platform (linux/Win32) nonblocking C++ IO on stdin/stdout/stderr
Another alternative to using ncurses or threads is to use GNU Readline, specifically the part of it that allows you to register callback functions. The pattern is then:
Use select() on STDIN (among any other descriptors)
When select() tells you that STDIN is ready to read from, call readline's rl_callback_read_char()
If the user has entered a complete line, rl_callback_read_char will call your callback. Otherwise it will return immediately and your other code can continue.
Let`s see how it done in one of Linux utilites. For example, perf/builtin-top.c sources (simplified):
static void *display_thread(void *arg)
{
struct pollfd stdin_poll = { .fd = 0, .events = POLLIN };
struct termios save;
set_term_quiet_input(&save);
while (!done) {
switch (poll(&stdin_poll, 1, delay_msecs)) {
...
}
}
tcsetattr(0, TCSAFLUSH, &save);
}
So, if you want to check if any data available, you can use poll() or select() like this:
#include <sys/poll.h>
...
struct pollfd pfd = { .fd = 0, .events = POLLIN };
while (...) {
if (poll(&pfd, 1, 0)>0) {
// data available, read it
}
...
}
In this case you will receive events not on each key, but on whole line, after [RETURN] key is pressed. It's because terminal operates in canonical mode (input stream is buffered, and buffer flushes when [RETURN] pressed):
In canonical input processing mode, terminal input is processed in
lines terminated by newline ('\n'), EOF, or EOL characters. No input
can be read until an entire line has been typed by the user, and the
read function (see Input and Output Primitives) returns at most a
single line of input, no matter how many bytes are requested.
If you want to read characters immediately, you can use noncanonical mode. Use tcsetattr() to switch:
#include <termios.h>
void set_term_quiet_input()
{
struct termios tc;
tcgetattr(0, &tc);
tc.c_lflag &= ~(ICANON | ECHO);
tc.c_cc[VMIN] = 0;
tc.c_cc[VTIME] = 0;
tcsetattr(0, TCSANOW, &tc);
}
Simple programm (link to playground):
#include <stdio.h>
#include <unistd.h>
#include <sys/poll.h>
#include <termios.h>
void set_term_quiet_input()
{
struct termios tc;
tcgetattr(0, &tc);
tc.c_lflag &= ~(ICANON | ECHO);
tc.c_cc[VMIN] = 0;
tc.c_cc[VTIME] = 0;
tcsetattr(0, TCSANOW, &tc);
}
int main() {
struct pollfd pfd = { .fd = 0, .events = POLLIN };
set_term_quiet_input();
while (1) {
if (poll(&pfd, 1, 0)>0) {
int c = getchar();
printf("Key pressed: %c \n", c);
if (c=='q') break;
}
usleep(1000); // Some work
}
}
Not entirely sure what you mean by 'console IO' -- are you reading from STDIN, or is this a console application that reads from some other source?
If you're reading from STDIN, you'll need to skip fread() and use read() and write(), with poll() or select() to keep the calls from blocking. You may be able to disable input buffering, which should cause fread to return an EOF, with setbuf(), but I've never tried it.

Resources