Should I reset termios settings on SIGINT/SIGTERM? - c

I was playing around with termios and I figured out quickly that if I change the terminal settings and exit, my changes will persist and screw up my environment. So I setup my program to save the initial settings with tcgetattr and reset them before exiting.
I predicted, however, that if I hit Ctrl-C to send SIGINT while my program was running that it would cause the terminal to still have my modified settings since my program wouldn't have executed the code to reset them back to the old settings.
But that didn't happen. In both Ubuntu and macOS Sierra, my terminal settings were reverted as if I had reset them in the program.
So the question is: Is this behavior something I can count on in general? Or does it make sense to register signal handlers to catch SIGINT/SIGTERM and revert terminal settings before exiting?
Code
Answering this question probably doesn't require looking at code, but here is my example, in case you're curious:
#include <stdio.h>
#include <string.h>
#include <termios.h>
int main() {
// put terminal into non-canonical mode
struct termios old;
struct termios new;
tcgetattr(0, &old);
new = old;
new.c_lflag &= ~(ICANON | ECHO);
tcsetattr(0, TCSANOW, &new);
// loop: get keypress and display (exit via 'x')
char key;
printf("Enter a key to see the ASCII value; press x to exit.\n");
while (1) {
key = getchar();
printf("%i\n", (int)key);
if (key == 'x') { break; }
}
// set terminal back to canonical
tcsetattr(0, TCSANOW, &old);
return 0;
}

I was a bit surprised to see that in my Arch Linux terminal settings also "were reverted". But in fact they remained the same. When I changed your code I managed to track some anomaly.
//...
new.c_lflag &= ~(ICANON | ECHO);
new.c_cc[VMIN] = 0;
new.c_cc[VTIME] = 0;
//...
So here if you don't press any button the output is -1. If you hit Ctrl-C, recompile and launch the original program (from the same terminal), it will also print -1, so there is no automatic reset.
I don't know why ECHO is "hidden" and I'd like to know, but I suggest you to manually revert all terminal settings.

Related

I'm writing a C program and I can't figure out how to insert "Press any key to continue..." and have it continue after any key and not just enter

I'm writing a menu driven program with a switch statement and while loop. My prof wants us to include "Press any key to continue..." and says to use getchar() for this. He says we will lose points if it requires us to press enter, which is what happens for me when I use the getchar().
I have seen many posts about using getch() with , however this is not working because I am using a C program compiled and executed with cygwin.
Thanks in advance
EDIT
Updating since I have not seen any answer for this specific question using Unix/Linux...
Emailed my prof and he got back to me with this...
"Unix/Linux issues. The keyboard input/output is buffered, i.e., your program might not get the character immediately after a user presses a key. Usually, the Operating System (OS) will send characters pressed after an ENTER key is pressed. If you want your program to get the character immediately after the user pressed it, run the following command in your shell (command window or Terminal). You only need to run it once after you first open your shell
stty -icanon min 1
I did this command in the shell before compiling and it still didn't work.
I then used getchar(); twice in a row since a scanf was used previously and doesn't consume the enter used and passes it to the first getchar();
Now it works and I can press ANY key to continue...
For Linux I use the following code.
#include <stdio.h>
#include <termios.h>
#include <unistd.h>
int getch (void)
{
// Declare variables.
int c = {0};
struct termios oldt = {0};
struct termios newt = {0};
// Get terminal attributes.
tcgetattr(STDIN_FILENO, &oldt);
// Store attributes.
newt = oldt;
// Set terminal to non-buffered.
newt.c_lflag &= ~(ICANON | ECHO);
tcsetattr(STDIN_FILENO, TCSANOW, &newt);
// Get user input.
c = getchar();
// Reset terminal attributes.
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
// Return character.
return(c);
}
For Windoze, just use #include <conio.h>.

Get Keyboard Interrupt in C

Program:
#include<stdio.h>
void main()
{
int time=1800;
while(1){
system("clear");
time-=1;
printf("%d\n",time);
sleep(1);
if(time==0)
pause();
}
}
The above program stops when the time reaches 0. My requirement is during the runtime of the program, If I press any key like spacebar or any other key, the program gets paused and once again I press the key, the program gets resumed. So for doing this, before execution of
while condition, we submit the signal handler for keyboard interrupt. In C how to do this.
What is the function used to get keyboard interrupt. I dont want to get input from the user, I want to handle the interrupt generated by the user through keyboard.
Thanks in Advance..,
You need conio.h for your requirement.It defines kbhit() and getch() both wait for input from keyboard.
Whenever kbhit() is called, it checks the keyboard buffer and returns a nonzero value if the buffer has any keypress otherwise 0 is returned.
The conio.h is used by MSDOS compilers and is not the part of standard C libraries (ISO). It is also not defined in POSIX.
#include<stdio.h>
#include<conio.h>
int main()
{
while(1)
{
while(!kbhit())
{
//works continuously until interrupted by keyboard input.
printf("M Tired. Break Me\n");
}
getch();
}
return 0;
}
For linux you may use the following snippet to implement kbhit() by using fnctl() from fnctl.h for signal handling:
#include <termios.h>
#include <unistd.h>
#include <fcntl.h>
int kbhit(void)
{
struct termios oldt, newt;
int ch;
int oldf;
tcgetattr(STDIN_FILENO, &oldt);
newt = oldt;
newt.c_lflag &= ~(ICANON | ECHO);
tcsetattr(STDIN_FILENO, TCSANOW, &newt);
oldf = fcntl(STDIN_FILENO, F_GETFL, 0);
fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);
ch = getchar();
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
fcntl(STDIN_FILENO, F_SETFL, oldf);
if(ch != EOF)
{
ungetc(ch, stdin);
return 1;
}
return 0;
}
The keyboard does not exist in purely standard C99 or C11 (stdin is not a keyboard, and could be a pipe(7) so is not always a tty(4); you might read from /dev/tty ...).
So it is much less simple that what you want it to be, and it is operating system specific. I am focusing on Linux.
Read much more about ttys, notably read the tty demystified. Notice that tty are usually in cooked mode, where the kernel is buffering lines (in addition of stdin being line buffered).
The reasonable way is to use a terminal library like ncurses or readline. These libraries are setting the tty in raw mode (which you might do by yourself, see this and termios(3); you'll probably need also to poll(2)). Be sure to exit properly and reset the tty to cooked mode before exiting.
But your life is probably too short to dive into termios(3) and tty_ioctl(4) so just use ncurses or readline
You could also consider some GUI application (e.g. above X11 or Wayland). Then use a toolkit (GTK, Qt, ...).
My requirement is during the runtime of the program, If I press any key like spacebar or any other key, the program gets paused and once again I press the key, the program gets resumed.
You can achieve this with this type of code
#include <stdio.h>
int main()
{
char i;int y=0;
while(1)
{
if(!(_kbhit()))
{
y=0;
printf("//key is not pressed");
//do what you want
}
else
{
printf("key pressed (execution pause) , press again to resume");
i=_getch();
y=1;
}
if(y==1)
{
getch();
}
}
return 0;
}

Reading a Stop Signal within a loop

I am using a while loop which doesnt terminate, for reproducing Tail command of unix using C code. I need a way to stop the loop apart from Ctrl + C which quits the process i believe. Is there any way to read Keyboard commands when used within the code ? The problem with using getchar() is that it stops the loop from running until a char is entered. Is there any alternative solution to this issue ?
You need to turn off blocking and line buffering. Turn off blocking so getc() returns right away. It will return -1 until it has a real character. Turn off line buffering so the OS sends the char right away instead of buffering it up until it has a full line which occurs when you press return.
#include <unistd.h> /* UNIX standard function definitions */
#include <fcntl.h> /* File control definitions */
#include <termios.h> /* POSIX terminal control definitions */
int main(void) {
// Turn off blocking
fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK);
struct termios options, oldoptions;
tcgetattr(STDIN_FILENO, &options);
// Disable line buffering
options.c_lflag &= ~( ICANON);
// Set the new options for the port...
tcsetattr(STDIN_FILENO, TCSANOW, &options);
while(1) {
char c = getc(stdin);
if(c != -1) break;
}
// Make sure you restore the options otherwise you terminal will be messed up when you exit
tcsetattr(STDIN_FILENO, TCSANOW, &oldoptions);
return 0;
}
I agree with the other posters that you should use signals, but this is the answer to what you asked.
This sounds very much like this question from the comp.lang.c FAQ.
Q: How can I read a single character from the keyboard without waiting for the RETURN key? How can I stop characters from being echoed on the screen as they're typed?

Capturing input without \n

I am making a simple 2d game in the terminal, and I have been wondering how I could get stdin without having to return. So, instead of the user having to press w\n (\n for return), they would just press 'w' and it would go forwards.
scanf, gets, and getchar cannot do this, but I have seen it be done before in programs such as Vi. How would I achieve this?
You need to set your terminal to non-canonical mode. You can use functions like tcsetattr, and tcgetattr to set and get terminal attributes. Here is a trivial example:
int main(int argc, const char *argv[])
{
struct termios old, new;
if (tcgetattr(fileno(stdin), &old) != 0) // get terminal attributes
return 1;
new = old;
new.c_lflag &= ~ICANON; // turn off canonical bit.
if (tcsetattr(fileno(stdin), TCSAFLUSH, &new) != 0) // set terminal attributes
return 1;
// at this point, you can read terminal without user needing to
// press return
tcsetattr(fileno(stdin), TCSAFLUSH, &old); // restore terminal when you are done.
return 0;
}
For more info about these functions, see glibc documentation. Especially this part.

Non-blocking stdio

I'm working on a program which will be taking in user input from the console as well as printfing out in a separate thread. I want to avoid situations where the user is halfway through typing something in and a printf comes along and prints itself at the cursor.
Is there a way to do non-blocking io in c from the console window? Ideally, capturing keypresses or something like that such that what the user types doesn't appear on the screen. I'm developing in Ubuntu, and it's best if I don't have to use things like ncurses.
using termios you can disable terminal echoing:
#include <termios.h>
struct termios oflags, nflags;
tcgetattr(fileno(stdin), &oflags);
nflags = oflags;
nflags.c_lflag &= ~ECHO;
nflags.c_lflag |= ECHONL;
if (tcsetattr(fileno(stdin), TCSANOW, &nflags) != 0) {
/* handle error */
}
then before exit (use atexit) you must restore the terminal:
if (tcsetattr(fileno(stdin), TCSANOW, &oflags) != 0) {
/* handle error */
}
Here's an example of how to turn off echo from C, taken directly from an HP forum (and I haven't personally tested it):
Okay this should be a simple example
of turning off echo:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <termios.h>
#define STDIN_FDES 0
struct termios save;
int main(int argc, char *argv[])
{
int cc = 0;
char s_tmp[80],*p = NULL;
struct termios work;
cc = tcgetattr(STDIN_FDES,&save);
work = save;
work.c_lflag &= ~(ECHO);
cc = tcsetattr(STDIN_FDES,TCSANOW,&work);
(void) printf("\nEnter value: ");
(void) fflush(stdout);
p = fgets(s_tmp,sizeof(s_tmp),stdin);
if (p != NULL) (void) printf("Out -> %s\n",p);
cc = tcsetattr(STDIN_FDES,TCSANOW,&save);
return(cc);
}
NOTE: It is very important that you
have signal handlers to catch SIGINT,
SIGTERM, ... and reset the terminal
using the original termios because the
last tcsetattr() wins and this applies
to the terminal device NOT simply the
process. If you leave the process with
echo off, it will be off in the shell
as well.
Otherwise, if Bash is a suitable approach, apparently you can just do stty -echo.
Turning off echo or using non-blocking I/O isn't the answer, if I understand your question correctly. Rather, you want to prevent a background thread from interrupting a user input thread, right?
For that, you'll need access to raw keypresses instead of line-buffered input. I don't know why you're allergic to ncurses or similar libraries; that's what they're for! I guess you could do it with termios or ioctl calls, if that's how you roll....
But to solve your multi-threaded TTY output problem, you could do this:
1) Create a mutex to control who can access the console
In the background thread, to output a message:
Grab the mutex; write the message; release the mutex; go back to sleep!
In the user input thread:
Grab the mutex when new input is detected. Keep exclusive access until the user hits enter,
then release the mutex and give the background thread a chance to talk.
Does that help?

Resources