Closing then opening standard input in C under Linux - c

I have a C console application under Linux (Raspbian - Raspberry Pi). The program has some character animation - it prints out messages character by character. For example, the text
Please give me your name!
is printed out completely after 5 seconds (char by char).
Afterwards, the user is asked to type in their name, which works just fine if the user waits for the message to be printed out. However, if the user is impatient and hits the keys on the keyboard randomly, the input request later is compromised.
Imagine the user types in
abc\nefgh\n
while the above text is being echoed ('\n' means new line - enter). If that happens, the user will not be asked to properly type in his/her name, but the array of characters 'abc' will be accepted as input and gets validated.
My question is how to disable input (buffering) temorarily (on Linux). I have tried several methods and read numerous posts about doing so, but none of them worked for my purpose.
The function that is responsible for asking for input is as follows:
int getLine(char *s, int length)
{
int i;
char c;
for (i = 0; i < length && (c = getchar()) != EOF && c != '\n'; i++)
{
if (c == '\0')
{
i--;
}
else
{
s[i] = c;
}
}
s[i] = '\0';
while (c != EOF && c != '\n')
{
c = getchar();
}
return i;
}
Empty lines are eliminated with the help of a while loop in the function calling the one above so enter inputs are considered invalid.
I tried closing stdin, but could not reopen it:
fclose(stdin);
and emptying buffer before the getLine:
char buf[BUFSIZ];
while (c = fgets(buf, BUFSIZ, stdin) != NULL);
Unfortunately, it did not work as it does with text files.
fflush(stdin); did not work either and is not pretty anyway.
My goal is to prevent users from typing in anything while the text is being written out and to ignore/close input buffering (stdin) temporarily. Also, it would be great to disable outputting (flushing) user inputs during printing as it gets displayed on Linux terminal.

You may do this by interacting with TTY directly. Look into source code of passwd or similar utilities for inspiration.
Function below clears TTY input buffer (error handling omitted). You need to call it just before reading user input.
#include <sys/ioctl.h>
#include <termios.h>
void clear_user_input() {
if (isatty(STDIN_FILENO)) {
int fd = open(ttyname(STDIN_FILENO), O_RDONLY);
ioctl(fd, TCFLSH, TCIFLUSH);
close(fd);
}
}

Related

Accepting a single character in C?

What I intend to do is to get the character entered and used it as a pattern. I've tried using getchar() but it won't work. I've hear of using scanf but it skips and stops whenever I press "shift" for the special characters on my keyboard.
int i, j, n;
char c;
c = getchar();
printf("Enter value of n: ");
scanf("%d", &n);
printf("Enter a Character: ");
getchar();
for(i=1; i<=n; i++)
{
for(j=1; j<=i; j++)
{
printf("%c", c);
}
printf("\n");
}
You need to assign the value returned by getchar to the variable c, and you had a redundent call to getchar that's why it skips reading the desired input:
int i, j, n;
char c;
printf("Enter value of n: ");
scanf("%d", &n);
printf("Enter a Character: ");
scanf(" %c", &c);
for(i=1; i<=n; i++)
{
for(j=1; j<=i; j++)
{
printf("%c", c);
}
printf("\n");
}
You can use %c with scanf:
scanf("%d %c", &n, %c);
This eliminates the need for the two getchar calls.
The space is required; it tells scanf to skip whitespace.
The problem you have is that your assumptions on getchar(3) are incorrect. You think getchar() is going to return the next key pressed in the input stream, but you are incorrectly assuming that it will be done without buffering or system processing (the terminal driver gives the program complete lines, or even worse, if you are reading from a file, complete buffer blocks, that have to be buffered so you miss no characters from the input stream)
You are assuming incorrectly that the end of line you need to press for the input to be feeded to the program does not count in the input stream.
What actually happens is:
you feed a complete line (because the kernel driver works that way) so you press your character, and then you see nothing, not after you have pressed the return key.
once you press it, you have more than one character (depending on how many you pressed before hitting the return key) that will stay in the buffer, until they are so consumed by the program. Normally this happens when you have executed more getchar() or scanf() statements.
The idea of this buffering mechanism is to allow a programmer to process character by charcacter large amounts of text, without the overhead of making a system call per character reading (this is a costly operation) so think of getchar() not as a sample function to get new users introduced to the world of programming, but as a hint to experienced programmers to use efficiently without having to think on buffering large amounts of text.
With stdio package, every character counts, so you have to think slowly and minuciously when you feed input to getchar(3).
The next question is: Right, then how can I solve and stop my program until I press some key? The first answer, with the set of tools you have exposed here is, be careful on what you input, instead of asking for any key, ask the user to press the return key, and then, do something like:
printf("Hit <ENTER> to continue"); fflush(stdout); /* so we get the line out, bypassing the buffering mechanism */
int c;
while ((c = getchar()) != EOF && c != '\n') {
/* just ignore the character we have received */
}
/* c == '\n' || c == EOF, so we can continue */
or, if you prefer, you can write a function just to do this (as there can be so many criteria to implement it, nobody included such a function in the standard C library, my apologies for that. ;) )
void wait_for_enter()
{
/* I use stderr, for two reasons:
* o stderr is normally unbuffered, so there's no need to fflush()
* o stdout can be redirected, so the prompt will not be visible in
* case you want to save the output of your program.
*/
fprintf(stderr, "Hit <ENTER> to continue");
int c;
while ((c = getchar()) != EOF && c != '\n') {
/* just ignore the character we have received
* until we get the end of file (ctrl-d at the terminal)
* or a new line */
}
/* c == '\n' || c == EOF, so we can continue */
/* it's assumed that the user pressed the enter key, so the echoed
* enter already did a newline, no need to do it here */
} /* wait_for_enter */
In order to wait for any character and in raw mode, you need first to ensure your input comes from a terminal (you cannot do the following on a normal file), then you have to switch the terminal driver to raw mode, so each character is given immediately to the program and no line editing processing is done, and then set the stdin descriptor to no buffering at all. Only then, you can receive individual characters with getchar(3), one by one, as they are keyed in. I think this is far out of the scope of this question, as the code to do that is far more complex than the above.
EDIT
Following is a complete sample of a program that uses raw input to process characters as they are keyed in.
/* pru.c -- program to show raw input from the terminal.
* Author: Luis Colorado <luiscoloradourcola#gmail.com>
* Date: Fri Sep 20 08:46:06 EEST 2019
* Copyright: (C) 2019 Luis Colorado. All rights reserved.
* License: BSD.
*/
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h> /* see termios(3) for a description on terminal conf */
#define F(_fmt) __FILE__":%d:%s: " _fmt, __LINE__, __func__
/* this function switches the terminal into raw mode and returns a malloc(3)ed
* terminal configuration, so it can be later restored. BEWARE that the returned
* configuration info must be deallocated by free(3) once it's not needed anymore.
* In case of failure of any system call, the function returns NULL, and errno is
* set to the failing cause. */
struct termios *set_raw(int fd)
{
struct termios *ret = malloc(sizeof *ret), cfg;
if (!ret) return NULL;
int res = tcgetattr(fd, &cfg);
if (res < 0) goto error;
*ret = cfg; /* save it for return */
cfmakeraw(&cfg);
/* set it after all buffered characters in the driver have drained out */
res = tcsetattr(fd, TCSADRAIN, &cfg);
if (res < 0) goto error;
return ret;
error:
free(ret);
return NULL;
} /* set_raw */
/* restores the configuration back to the associated file descriptor */
int restore_cfg(int fd, struct termios *cf)
{
/* set it after all buffered characters in the driver have drained out */
return tcsetattr(fd, TCSADRAIN, cf);
} /* restore_cfg */
int main()
{
struct termios *cfg = set_raw(fileno(stdin));
if (!cfg) {
fprintf(stderr, F("stdin: %s\n"),
strerror(errno));
}
setbuf(stdin, NULL); /* stdin unbuffered */
setbuf(stdout, NULL); /* stdout unbuffered */
/* BEWARE that raw mode doesn't process any characters, so no Ctrl-C(interrupt), Ctrl-D(EOF), etc.
* will be available, only if you read from a file, you'll get EOF, but you'll not be able to produce
* that on the terminal, you'll need to swith to another console and kill the process. */
int c;
while ((c = getchar()) != EOF && c != '\033') { /* ESCAPE key('\033') is a safeguard to end input */
/* print the input char as an hex number */
printf("[%02x]", c);
}
if (cfg) { /* if we were able to set the terminal to raw mode */
/* restore config */
restore_cfg(fileno(stdin), cfg);
/* and free it */
free(cfg);
}
exit(EXIT_SUCCESS);
} /* main */
The full source code can be also downloaded from here.
You can use this program to see how input keys get mapped into characters, as you'll note that when you press the enter key, the raw input is [0d] (ascii char 13, CARRY RETURN) while in normal line mode you get '\n' which is [0a] or ASCII LINE FEED, instead (you can check this if you redirect input from the pru.c text file). Also you'll see that you are unable to specify EOF from the terminal driver with Ctrl-D and that Ctrl-C does not come to help. Well, I have included a safeguard, by ending the program in case you press the ESC key, which generates an ASCII ESCAPE character (\033). This is also commented in the source code.
All of this processing is done by the kernel driver, so all unix implementations get the same line end characters or interpret the control characters the same way.

How to display asterisks for password input only after two consecutive colons?

I want to read input from user. After the user typed the sequence ::, the rest of the input should be asterisks.
For example: let's say user typed: Alex::vn800. On the screen, the output should be: Alex::*****.
I have a function that reads input from user and display * on screen, but I didn't managed to use it in a middle of reading line.
I tried to manipulate functions getchar() and scanf() to stop reading line after detecting a sequence of ::, and then call the function but nothing worked.
What can I do?
Update: Hey! thanks for the answers.
I fainlly solved the problem by using the library conio.h - like in any other simple get-password code, just that I saprated it for cases according to what I want the screen will show and not just '*' for any case.
If it's not strictly necessary to have both username and password in the same line, I would suggest simply getting the username first and then using the getpass() function, like here.
I've tried ataman's method, but it didn't work on OSX 10.9.
Here's a modified version, following goldPseudo's approach:
#include <stdio.h>
#include <stdlib.h>
int main() {
int readChar;
int status = 0;
int semicolonCount = 0;
system ("/bin/stty raw"); // disable buffering and other stuff
while ((readChar = getchar()) && (readChar != 13 /* ENTER keycode */))
{
if (status == 0)
{
printf("%c", readChar);
if (readChar == ':')
{
semicolonCount++;
} else {
semicolonCount = 0;
}
if (semicolonCount == 2)
{
status = 1;
}
} else {
printf("*");
}
}
printf("\r\n"); // print new line
system ("/bin/stty cooked"); // reenable buffering, might not be the original mode the terminal was in
return 0;
}
The problem with this approach is that, since you are in "raw mode", special characters, like BACKSPACE, ENTER, Ctrl+D and even Ctrl+C, are not processed.
You would have to implement the behaviour for those characters yourself.

Why EOF(end of file) isn't working at the end of a line without a '\n' before it?

So I started to learn C using the ANSI C book. One of the early exercises in the book is to write a program that takes text input and prints every word on a new line, simple enough. So i did:
#include <stdio.h>
#define IN 1
#define OUT 0
main() {
int c;
int state;
state = OUT;
while((c = getchar()) != EOF){
if(c != ' ' && c != '\n' && c != '\t'){
state = IN;
}else if(state == IN){
state = OUT;
putchar('\n');
}
if(state == IN)
putchar(c);
}
getchar();
}
The thing is that while the program works fine it won't break from the while loop if I enter EOF(Ctrl+Z on windows) as the last char of a line or in the middle of it.
So I found an answer here.
What I learned is that the (Ctrl+Z) char is some sort of signal to end the stream and it must be on a new line for getchar() to return EOF. While this is all good and it kinda helped I really want to know why is it necessary for the EOF to be on its own line?
The problem you are having is related to your command line terminal and has nothing to do with the end of file marker itself. Instead of sending characters to the program as you type them, most terminals will wait until you finish a whole line before sending what you type to the program.
You can test this by having the input come from a text file instead of being typed by hand. You should be able to end the input file without a newline without any problems.
./myprogram.exe < input.txt
By the way, the answer you linked to also points out that EOF is not a character that is actually in your input stream, so there is no way for it to come "before" a "\n". EOF is just the value that getchar returns once there are no characters left to be read.
When reading from a tty device (such as stdin for a program running in a console or terminal window) the terminal is in so-called cooked mode. In this mode, some level of line editing facilities are provided, allowing the user to backspace and change what has been typed.
The characters that are typed are not returned to the program until after return has been pressed.
It is possible to do this by placing the terminal in 'raw' mode. Unfortunately it seems this is not well standardised though, so it is somewhat system specific. The answers to this question have some examples for various platforms.

How to echo stdin to stdout

it's a very simple question, how to echo every char that I type in stdin to stdout? I'm trying to do it for a long time and I've tried many ways and no one works well. The best I could do was to get what I typed after application end.
The best I did was:
#include <stdio.h>
int main()
{
while (1)
{
int ch = getc(stdin);
if(ch == EOF) break;
putc(ch, stdout);
}
return 0;
}
Thanks.
You need to flush the stdout:
int main()
{
while (1)
{
int ch = getc(stdin);
fflush(stdout);
if(ch == EOF) break;
putc(ch, stdout);
}
return 0;
}
The code you have should work just fine, as long as you hit enter. In most systems, the program will get input in a line oriented fashion. If you want to echo the key immediately after it is hit, you will need to change the input method for the program. On many systems, this would be getch(), but there may be other requirements you have to satisfy before you can use the interface (ncurses requires some additional setup, for example).
When echoing the data immediately after the key is hit, you will be required to flush the output in some way. If you are sending the output to stdout, then a call to fflush() will work. If you are using some system specific output command, you may be required to call some kind or window refresh routine.
I wonder if a better way would be:
int ch;
while((ch = getchar()) >= 0)
{
putchar(ch);
}
Then if you call this:
echo this is my input | ./myprogram
it would output the entire stdin this is my input without hitting the enter key.

Number Pad [Enter] not \n in C?

I'm working on a small C program for a college assignment and I've noticed a weird bug in my code. I use an iMac with the short keyboard generally, but its battery was flat so i plugged in a standard USB keyboard with number pad.
The weird thing is that if I hit [Enter] on my number pad, it seems to do what the regular [Enter} key does, but the \n I am trying to detect in the stdin function I made to read the keyboard input, doesn't work when I use the number pad's [Enter] key.
Wtf?
Here is my function that reads the user input:
/* This is my implementation of a stdin "scanner" function which reads
* on a per character basis until the the termination signals are found
* and indescriminately discarding all characters in the input in excess
* of the supplied (limit) parameter. Eliminates the problem of 'left-over'
* characters 'polluting' future stdin reads.
*/
int readStdin(int limit, char *buffer)
{
char c;
int i = 0;
int read = FALSE;
while ((c = myfgetc(stdin)) != '\n' && c != '\0') {
/* if the input string buffer has already reached it maximum
limit, then abandon any other excess characters. */
if (i <= limit) {
*(buffer + i) = c;
i++;
read = TRUE;
}
}
/* clear the remaining elements of the input buffer with a null character. */
for (i = i; i < strlen(buffer); i++) {
*(buffer + i) = '\0';
}
return read;
}
/* This function used to wrap the standard fgetc so that I can inject programmable
* values into the stream to test my readStdin functions.
*/
int myfgetc (FILE *fin) {
if (fakeStdIn == NULL || *fakeStdIn == '\0')
return fgetc (fin);
return *fakeStdIn++;
}
NB: The myfgetc and the subsequent *fakeStdIn are part of a way that I can unit test my code and 'inject' items into the stdin stream programatically as someone suggested on this question: How do I write a testing function for another function that uses stdin input?.
What output do you get for this tiny test?
#include <stdio.h>
int main(int argc, char* argv[]) {
int c;
while((c=getchar()) != EOF) {
printf("%d\n", c);
}
return 0;
}
Could well be that on Mac, you are getting \r\n, not just \n.
So it turns out that it's a Mac OSX thing. I've spoken to other Mac users and they have the same problem. Never found a fix because one may simply not exist. The problem doesn't occur on Solaris machines and since that's the OS which the code will be run on, I guess it doesn't really matter.
I am going to answer this myself with the answer that its just one of those OSX "quirks" and be done with it.

Resources