How detect assigned terminal device for interactive work - c

I am writing pager pspg. There I have to solve following issue. After reading from stdin I should to reassign stdin from previous reading from pipe to reading from terminal.
I used
freopen("/dev/tty", "r", stdin)
But it doesn't work, when pager was used from command what was not executed directly
su - someuser -c 'export PAGER=pspg psql somedb'
In this case, I got a error: No such device or address.
I found a workaround - now, the code looks like:
if (freopen("/dev/tty", "r", stdin) == NULL)
{
/*
* try to reopen pty.
* Workaround from:
* https://cboard.cprogramming.com/c-programming/172533-how-read-pipe-while-keeping-interactive-keyboard-c.html
*/
if (freopen(ttyname(fileno(stdout)), "r", stdin) == NULL)
{
fprintf(stderr, "cannot to reopen stdin: %s\n", strerror(errno));
exit(1);
}
}
What is a correct way to detect assigned terminal device in this case?
But this workaround is not correct. It fixed one issue, but next is comming. When someuser is different than current user, then reopen fails with error Permission denied. So this workaround cannot be used for my purposes.

What less does in this situation is fall back to fd 2 (stderr). If stderr has been redirected away from the tty, it gives up on trying to get keyboard input, and just prints the whole input stream without paging.
The design of su doesn't allow for anything better. The new user is running a command on a tty owned by the original user, and that unpleasant fact can't be entirely hidden.
Here's a nice substitute for su that doesn't have this problem:
ssh -t localhost -l username sh -c 'command'
It has a little more overhead, of course.

On the end I used pattern that I found in less pager, but modified for using with ncurses:
First I try to reopen stdin to some tty related device:
if (!isatty(fileno(stdin)))
{
if (freopen("/dev/tty", "r", stdin) != NULL)
noatty = false;
/* when tty is not accessible, try to get tty from stdout */
else if (freopen(ttyname(fileno(stdout)), "r", stdin) != NULL)
noatty = false;
else
{
/*
* just ensure stderr is joined to tty, usually when reopen
* of fileno(stdout) fails - probably due permissions.
*/
if (!isatty(fileno(stderr)))
{
fprintf(stderr, "missing a access to terminal device\n");
exit(1);
}
noatty = true;
fclose(stdin);
}
}
else
noatty = false;
When I have not tty and cannot to use stdin, then I am using newterm functions, that allows to specify input stream:
if (noatty)
/* use stderr like stdin. This is fallback solution used by less */
newterm(termname(), stdout, stderr);
else
/* stdin is joined with tty, then use usual initialization */
initscr();

Related

fprintf() to stdout not working after creating and opening a FIFO

When my program starts, it just creates a fifo and opens it, after that I just want to output some information to the screen, however, nothing gets printed out. Here's a snippet of my code:
void listen(const server_config_t* server_conf)
{
// Create FIFO
if (mkfifo(SERVER_FIFO_PATH, 0660) == -1) {
fprintf(stdout, "server FIFO not created as it already exists. continuing...\n");
}
// Open FIFO (for reading)
int fd;
if ((fd = open(SERVER_FIFO_PATH, O_RDONLY)) == -1) {
// fprintf(stderr, "error: could not open server FIFO\n");
perror("FIFO");
exit(1);
}
// Open dummy FIFO (for writing, prevent busy waiting)
// TODO: find way to wait without another file descriptor?
int fd_dummy;
if ((fd_dummy = open(SERVER_FIFO_PATH, O_WRONLY)) == -1) {
perror("DUMMY FIFO");
exit(1);
}
// TODO: this should print immediately after starting,
// but doesn't for some reason
fprintf(stdout, "server listening... %d %s\n", server_conf->num_threads,
server_conf->password);
fflush(stdout);
.
.
.
}
Here's my output:
I've tried commenting out the fifo creation and opening, and when I do that the message gets printed correctly to the screen.
Opening a FIFO normally blocks until the other end is opened as well, see http://man7.org/linux/man-pages/man7/fifo.7.html. So your program probably waits in open(SERVER_FIFO_PATH, O_RDONLY) and does not reach any other fprintf or perror.
Your attempt to open the FIFO for reading first and then for writing does not work because the first open does not return.
You should be able to see this when you step through your program using a debugger.
BTW: When mkfifo returns -1 you should check if errno is EEXIST. There could be other errors that would also result in return value -1, see https://linux.die.net/man/3/mkfifo
As you can see from your output, there is blocking. That is, your current process cannot go on until the other end of the FIFO is opened for write. You should glance at the man page.
As to your error, there are two cases maybe the directory into which you want to place the FIFO doesn't permit to do that. Second case may be due to a system error. To overcome the issue, you need to change your fprintf as following.
#include <string.h>
#include <stdlib.h>
..
..
fprintf(stderr, "server FIFO not created as it already exists. Error: %s\n", strerror(errno));
exit(EXIT_FAILURE);

check if FILE* is stdout; portable?

I am currently writing a piece of code whose intended usage is this:
program input.txt output.txt
or
program input.txt
in which case it defaults to stdout.
This is the code I have now (within main()):
FILE *outFile;
if (argc < 3) {
outFile = stdout;
} else {
fprintf(stdout, "Will output to file %s\n", argv[2]);
outFile = fopen(argv[2], "w");
if (outFile == NULL) {
fprintf(stderr, "ERR: Could not open file %s. Defaulting to stdout\n", argv[2]);
outFile = stdout;
}
}
/* ... write stuff to outFile... */
if (argc < 3 && outFile != stdout) {
fclose(outFile);
}
These are my concerns: first of all, will this successfully open and close outFile when provided? Also, will this successfully not close stdout? Can anything bad happen if I close stdout?
Also, is this portable? I compile with gcc but this project will be evaluated by a professor using Windows.
Apologies if this is a bit of a mess of a question. I come from Python and am not a CS major (I'm studying mathematics).
Yes, it's portable and it's okay.
Yes, it's portable. You assigned outfile = stdout, so they will be equal as long as you don't reassign either of them elsewhere in the program.
You don't really need the argc < 3 test as well -- the two conditions should always be the same, since you only do the assignment when that's true.
In any program that writes significant data to stdout, you should close stdout immediately before exiting, so that you can check for and report delayed write errors. (Delayed write errors are a design mistake; it ought to be impossible for fclose or close to fail. But we are stuck with them.)
The usual construct is, at the very end of main,
if (ferror(stdout) || fclose(stdout)) {
perror("stdout: write error");
return 1;
}
return 0;
Some programs stick an fflush in there too, but ISO C requires fclose to perform a fflush, so it shouldn't be necessary. This construct is entirely portable.
It's important for this to be the very last thing you do before exiting. It is relatively common for libraries to assume that stdout is never closed, so they may malfunction if you call into them after closing stdout. stdin and stderr are also troublesome that way, but I've yet to encounter a situation where one wanted to close those.
It does sometimes happen that you want to close stdout before your program is completely done. In that case you should actually leave the FILE open but close the underlying "file descriptor" and replace it with a dummy.
int rfd = open("/dev/null", O_WRONLY);
if (rfd == -1) perror_exit("/dev/null");
if (fflush(stdout) || close(1)) perror_exit("stdout: write error");
dup2(rfd, 1);
close(rfd);
This construct is NOT portable to Windows. There is an equivalent, but I don't know what it is. It's also not thread-safe: another thread could call open in between the close and dup2 operations and be assigned fd 1, or it could attempt to write something to stdout in that window and get a spurious write error. For thread safety you have to duplicate the old fd 1 and close it via that handle:
// These allocate new fds, which can always fail, e.g. because
// the program already has too many files open.
int new_stdout = open("/dev/null", O_WRONLY);
if (new_stdout == -1) perror_exit("/dev/null");
int old_stdout = dup(1);
if (old_stdout == -1) perror_exit("dup(1)");
flockfile(stdout);
if (fflush(stdout)) perror_exit("stdout: write error");
dup2 (new_stdout, 1); // cannot fail, atomically replaces fd 1
funlockfile(stdout);
// this close may receive delayed write errors from previous writes
// to stdout
if (close (old_stdout)) perror_exit("stdout: write error");
// this close cannot fail, because it only drops an alternative
// reference to the open file description now installed as fd 1
close (new_stdout);
Order of operations is critical: the open, dup and fflush calls must happen before the dup2 call, both close calls must happen after the dup2 call, and stdout must be locked from before the fflush call until after the dup2 call.
Additional possible complications, dealing with which is left as an exercise:
Cleaning up temporary fds and locks on error, when you don't want to stop the whole program on error
If the thread might be canceled mid-operation
If a concurrent thread might call fork and execve mid-operation

Not able to read input from stdin/STDIN_FILENO in C?

I have this command line argument -
cat file_name | ./a.out
The problem is not reading from the cat command inside the C program as we can do that with read(), fgets(), fgetc() but the actual problem I am facing is after reading the data from cat I am not able to take input from user using fgets.
Here is my sample code
while(fgets(buffer, BUFSIZ, stdin ) != NULL )
puts( buffer ); // Here I have tried strtok( buffer, "\n" ) too.
memset( buffer, 0, BUFSIZ );`
The problem is after this line, it is not asking for the input like the below is not working-
puts("Name: ");
fgets( buffer, BUFSIZ, stdin );
Help me with what's wrong happening here?
When you do cat file_name | ./a.out the standard input of your program is tied to a pipe linking it to the output of cat. Your program will never get to see the user input - the very stream from where it would arrive has been replaced by the aforementioned pipe.
Mind you, I suspect that with some horrible POSIX-specific trickery you may be able to reopen it going straight for the tty device, but it's just bad design. If you need to both read from a file and accept interactive user input just accept the file as a command line argument and use stdin to interact with the user.
Edit
This is an example of the Unix-specific kludges that one can attempt, assuming that the process still has a controlling terminal. After reading all the original stdin, I'm opening /dev/tty (which is the controlling terminal of the process) and re-linking stdin to it.
Disclaimer: this is for entertainment purposes only, don't do this for real.
#include <stdio.h>
#include <stdlib.h>
void die(const char *msg) {
fprintf(stderr, "%s\n", msg);
fputs(msg, stderr);
exit(1);
}
int main() {
/* Read all of stdin and count the bytes read (just to do something with it) */
int ch;
unsigned long count = 0;
while((ch = getchar())!=EOF) {
count++;
}
printf("Read %lu bytes from stdin\n", count);
/* Open the controlling terminal and re-link it to the relevant C library FILE *
* Notice that the UNIX fd for stdin is still the old one (it's
* surprisingly complex to "reset" stdio stdin to a new UNIX fd) */
if(freopen("/dev/tty", "r", stdin) == NULL) {
die("Failed freopen");
}
/* Do something with this newly gained console */
puts("How old are you?");
fflush(stdout);
int age = -1;
if(scanf("%d", &age)!=1) {
die("Bad input");
}
printf("You are %d years old\n", age);
return 0;
}
(previously I had a solution that checked if stderr or stdout were still consoles, which was even more of a kludge; thanks #rici for reminding me of the fact that POSIX has the concept of "controlling terminal", which is accessible through /dev/tty)
If you need to use stdin for user interaction, then you need to use a different file descriptor for reading the input stream.
You could use a specific pre-opened file descriptor and document that (e.g. "the input stream should be connected to fd 3"), but the usual approach is to accept a file name as a command-line argument. You can then provide a named pipe as the argument; shells such as Bash provide process substitution to make that easy:
./a.out <(cat file_name)
When that is run interactively like that, stdin is still connected to the terminal, and can be used at the same time as the stream from the connected command.
(Obviously, if the command actually is cat with a single argument, then you could just provide the filename itself as the argument, but I'm assuming that's a placeholder for a more involved pipeline).

Terminate cat command immediately after using in C

So I am communicating with a device by using echo to send and cat to receive. Here's a snippet of my code:
fp = popen("echo "xyz" > /dev/ttyACM0 | cat - /dev/ttyACM0", "r");
while (fgets(ret_val, sizeof(ret_val)-1, fp) != NULL)
{
if (strcmp(ret_val, "response") == 0)
{
close(fp);
return ret_val;
}
}
Ok, The problem is, cat seems to stay open, because when I run this code in a loop, it works the first time, then hangs at the spot I call popen. Am I correct in assuming cat is the culprit?
Is there a way to terminate cat as soon as I run the command, so I just get the response from my device? Thanks!
In the command:
echo "xyz" > /dev/ttyACM0 | cat - /dev/ttyACM0
TTY devices normally do not open until carrier is present, or CLOCAL is set. The cat could be waiting on open. Assuming the device opens, then the cat will hang waiting to read characters until either (1) it receives an EOF character such as control-D, or (2) carrier is lost or (3) you kill it.
Another problem here is that the pipe between echo and cat immediately closes, because the output of the echo is redirected to the same TTY device, and the redirection closes the pipe.
Generally TTY devices are ornery beasts and require special handling to get the logic right. Probably you are better to read up on TTY devices especially:
man termios
If you are doing something REALLY SIMPLE, you might get by with:
fp = popen("echo 'xyz' >/dev/ttyACM0 & (read x; echo \"$x\")");
Keep in mind that both the echo and the read might hang waiting for carrier and that you will get at most one line of output from the popen, and the read could hang waiting for an EOL character.
This whole approach is fraught with problems. TTY devices require delicate care. You are using a hammer in the dark.
There's no easy way to kill the process launched by popen, as there's no API to get the pid -- there's only pclose which waits until it ends of its own account (and youe should ALWAYS use pclose instead of fclose to close a FILE * opened by popen.)
Instead, you're probably better off not using popen at all -- just use fopen and write what you want with fputs:
fp = fopen("/dev/ttyACM0", "r+");
fputs("xyz\n", fp); // include the newline explicitly
fflush(fp); // always flush after writing before reading
while (fgets(ret_val, sizeof(ret_val)-1, fp) != NULL) {
:

storing logs/error message on C programming

When an error occurs, I would like my C code to store the error before exiting the program. Is it advised to store the stderr to a file (e.g., /home/logs.txt) or would it be advised to use a different method to keep the logs/error report (considering the programming environment is Linux). E.g., for the code below, how I could apply the method to store the logs/error message on /home/log.txt or /home/log
FILE *fp1;
fp1 = fopen("/sys/class/gpio/export","w");
if(fp1 == NULL){
fprintf(stderr, "errno:%s - opening GPIO136 failed - line 739\n ", strerror(errno));
close(fp1);
exit(1);
}
Thank you.
If stderr is always used to print out all your error message, so, you can redirect output to a specific file.
$ program 2>~/logs.txt
For a better logging tool, you can use:
syslog standard function.
log4c library.
If you want to store the error, stderr is probably not a good choice because you'll need to pipe stderr to a file every time you run the program.
If you want to write to /home/log.txt, open a FILE pointer to it and write with fprintf the same way you tried to open /sys/class/gpio/export and write to that instead of stderr. Also be sure to open the log file with append mode.
FILE *fp1;
fp1 = fopen("/sys/class/gpio/export","w");
if(fp1 == NULL){
FILE *fpErr = fopen("/home/log.txt", "a");
if(fpErr != NULL)
fprintf(fpErr, "errno:%s - opening GPIO136 failed - line 739\n ", strerror(errno));
close(fpErr);
close(fp1);
exit(1);
}

Resources