get fd of terminal when a destination of pipe - c

My program (a text editor) enters raw mode of terminal like this:
tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw)
so that it can read key strokes, draw using escape codes etc.
But now I want to do this: echo hello | myprog, to read stdin from a pipe and then display it in the program UI. But now STDIN_FILENO points to a pipe not to a terminal and tcsetattr fails with improper ioctl. How do I get fd of an actual terminal when being a destination of a pipe?

Usually, /dev/tty is the current console, and if you don't need stdio interfaces, you can open it, receiving a fd, with int ttyfd = open("/dev/tty", O_RDWR). (It's usually a bad idea to mix Unix I/O and stdio I/O on the same device. But if you really want to, open with FILE* ftty = fopen("/dev/tty", "rw"); and get the fd out of the FILE* with fileno(ftty).)

Related

Are stdin and stdout actually the same file?

I am completely confused, is it possible that stdin, stdout, and stderr point to the same filedescriptor internally?
Because it makes no difference in C if i want to read in a string from the console if I am using stdin as input or stdout.
read(1, buf, 200) works as read(0, buf, 200) how is this possible?
(0 == STDIN_FILENO == fileno(stdin),
1 == STDOUT_FILENO == fileno(stdout))
When the input comes from the console, and the output goes to the console, then all three indeed happen to refer to the same file. (But the console device has quite different implementations for reading and writing.)
Anyway, you should use stdin/stdout/stderr only for their intended purpose; otherwise, redirections like the following would not work:
<inputfile myprogram >outputfile
(Here, stdin and stdout refer to two different files, and stderr refers to the console.)
One thing that some people seem to be overlooking: read is the low-level system call. Its first argument is a Unix file descriptor, not a FILE* like stdin, stdout and stderr. You should be getting a compiler warning about this:
warning: passing argument 1 of ‘read’ makes integer from pointer without a cast [-Wint-conversion]
int r = read(stdout, buf, 200);
^~~~~~
On my system, it doesn't work with either stdin or stdout. read always returns -1, and errno is set to EBADF, which is "Bad file descriptor". It seems unlikely to me that those exact lines work on your system: the pointer would have to point to memory address 0, 1 or 2, which won't happen on a typical machine.
To use read, you need to pass it STDIN_FILENO, STDOUT_FILENO or STDERR_FILENO.
To use a FILE* like stdin, stdout or stderr, you need to use fread instead.
is it possible that stdin, stdout, and stderr point to the same filedescriptor internally?
A file descriptor is an index into the file descriptor table of your process (see also credentials(7)...). By definition STDIN_FILENO is 0, STDOUT_FILENO is 1, annd STDERR_FILENO is 2. Read about proc(5) to query information about some process (for example, try ls -l /proc/$$/fd in your interactive shell).
The program (usually, but not always, some shell) which has execve(2)-d your executable might have called dup2(2) to share (i.e. duplicate) some file descriptors.
See also fork(2), intro(2) and read some Linux programming book, such as the old ALP.
Notice that read(2) from STDOUT_FILENO could fail (e.g. with errno(3) being EBADF) in the (common) case where stdout is not readable (e.g. after redirection by the shell). If reading from the console, it could be readable. Read also the Tty Demystified.
There is nothing prohibiting any number of file-handles referring the same thing in the kernel.
And the default for a terminal-program is to have STDIN, STDOUT and STDERR refer to the same terminal.
So, it might look like it doesn't matter which you use, but it will all go wrong if the caller does any handle-redirection, which is quite common.
The most common is piping output from one program into the input of the next, but keeping stdout out of that.
An example for the shell:
source | filter | sink
Programs such as login and xterm typically open the tty device once when creating a new terminal session, and duplicate the file descriptor two or three times, arranging for file descriptors 0, 1 and 2 to be linked to the open file description of the opened tty device. They typically close all other file descriptors before exec-ing the shell. So if no further redirection is done by the shell or its child processes, the file descriptors, 0, 1 and 2, remain linked to the same file. Because the underlying tty device was opened in read-write mode, all three file descriptors have both read and write access.

I cannot write to stdin with /proc/{pid}/fd/0

I have this program:
#include <stdio.h>
int main() {
char buf[10];
puts("gimme input:");
fread(buf, 1, 10, stdin);
printf("got %s", buf);
}
When I run this and open another terminal I try to write to stdin:
echo "ASDFASDFASDF" > /proc/{pid}/0
ASDFSADFSADF gets printed on the terminal that is running my C program, but fread still doesn't return until I type in the actual terminal. It also does not print any of the text that I wrote to /proc/{pid}/0
Is there something else I have to do to programatically input text to stdin?
If stdin is a terminal, then writing something to stdin will write to the terminal. Reading from the terminal will read whatever is typed into the terminal, not what's written to the terminal. This is just how terminals work.
If you want a program to read from something other than a terminal, you have to direct that to happen. Or, if you want to use a virtual terminal that you can put information into it and have it be read out, you have to direct that to happen.
Probably the simplest solution is to create a pipe with mkpipe and have the program read from the pipe rather than a terminal.
When you execute the echo command output-ing to the File Descriptor 0 you're just sending text. If you check the file descriptor using ls -l probably it is pointing to an device TTY or PTY/PTS. If you check the FD type using lsof it will be tty. It means you need to interact with this FD such as TTY.
Basically you need to simulate the input to get the expected behavior.
You can do this by calling the kernel tool ioctl.tiocsti(). I added a python code into the following similar question: Writing to File descriptor 0 (STDIN) only affects terminal. Program doesn't read

Why is it possible to write() to STDIN?

I have the following code:
int main()
{
char str[] = "Hello\n";
write(0, str, 6); // write() to STDIN
return 0;
}
When I compiled and executed this program, Hello was printed in the terminal.
Why did it work? Did write() replace my 0 (STDIN) argument with 1 (STDOUT)?
Well, old Unix systems were originaly used with serial terminals, and a special program getty was in charge to manage the serial devices, open and configure them, display a message on an incoming connexion (break signal), and pass the opened file descriptors to login and then the shell.
It used to open the tty device as input/output to configure it, and that was then duplicated in file descriptors 0, 1 and 2. And by default the (still good old) stty command operates by default on standard input. To ensure compatibility, on modern Linuxes, when you are connected to a terminal, file descriptor 0 is still opened for input/output.
It can be used as a quick and dirty hack to only display prompts when standard input is connected to a terminal, because if standard input is redirected to a read only file or pipe, all writes will fail (without any harm for the process) and nothing will be printed. But it is anyway a dirty hack: just imagine what happens if a caller passes a file opened for input/output as standard input... That's why good practices recommend to use stderr for prompts or messages to avoid having them lost in redirected stream while keeping output and input in separate streams, which is neither harder nor longer.
TL/DR: if you are connected to a terminal, standard input is opened for input/output even if the name and standard usage could suggest it is read only.
Because by default your terminal will echo stdin back out to the console. Try redirecting it to a file; it didn't actually write to stdout.
Are you confusing write with fwrite? The first parameter in write is a "file descripter", but it's not stdin. Try doing an fwrite to stdin -- it doesn't happen.

Redirecting stdout to socket

I am trying to redirect stdout to a socket. I do something like this:
dup2(new_fd, STDOUT_FILENO);
After doing so all stdio functions writing to the stdout fail. I have tried to reopen stdout this way:
fclose(stdout);
stdout = fdopen(STDOUT_FILENO, "wb");
But printf and other functions still don't work.
EDIT:
I am affraid that I misunderstood the problem at the first place. After some more debugging I've figured out that this is a real issue:
printf("Test"); // We get Broken pipe here
// Reconnect new_fd
dup2(new_fd, STDERR_FILENO);
printf("Test"); // This also returns Broken pipe despite that stdout is fine now
Thanks.
1: on dup2(src, dst)
A number of operating systems track open files through the use of file descriptors. dup2 internally duplicates a file descriptor from the src to dst, closing dst if its already open.
What your first statement is doing is making every write to STDOUT_FILENO to go to the object represented by new_fd. I say object because it could be a socket as well as a file.
I don't see anything wrong with your first line of code, but I don't know how new_fd is defined.
2: on reopening stdout
When you close a file descriptor, the OS removes it from its table. However, when you open a file descriptor, the OS sets the smallest available file descriptor as the returned value. Thus, to reopen stdout, all you need to do is reopen the device. I believe the device changes depending on the OS. For example, on my Mac the device is /dev/tty.
Therefore, to reopen the stdout, you want to do the following:
close(1);
open("/dev/tty", O_WRONLY);
I've solved the problem by clearing a stdio's error indicator after fixing stdout:
clearerr(stdout);
Thanks for your help.

Does every process have its stdin stdout stderr defined as Keyboard, Terminal etc?

Does every process have stdin, stdout and stderr associated to it to the Keyboard and Terminal?
I have a small program. I want to replace the keyboard input to a file called new.txt. How do I go about it?
FILE *file1
fopen("new.txt", "r")
close(0); // close the stdio
dup2(file1, 0);
Would this work? Now my stdio is redirected to the FILE?
No, not every process. But on operating systems that give you a command-line window to type in, a program started from that command line will have stdin connected to the keyboard, and stdout and stderr both going to the terminal.
If one program starts another, then often the second program's standard streams are connected to the first program in some way; for example, the first program may have an open descriptor through which it can send text and pretend that it's the "keyboard" for the second process. The details vary by operating system, of course.
In response to your question:
Would this work ?
No. dup2() takes two file descriptors (ints) while you're passing it a FILE * and an int. You can't mix file handles (FILE *s) and file descriptors (ints) like that.
You could use open instead of fopen to open your file as a file descriptor instead of a file handle, or you could use fileno to get the file descriptor from a file handle. Or you could use freopen to reopen the stdin file handle to a new file.
Note that file descriptors (ints) are part of POSIX operating systems and are only portable to other POSIX systems, while file handles (FILE *s) are part of the C standard and are portable everywhere. If you use file descriptors, you'll have to rewrite your code to make it work on Windows.

Resources