How is second scanf working after closing standard input - c

How is the second scanf working in the below code
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main()
{
char buf[256];
int i;
write(1, "Hello World\n", strlen("Hello World\n"));
dup(0);
close(0);
scanf("%s", buf);
printf("Buffer:%s\n", buf);
dup(3);
scanf("%s", buf);
printf("Buffer:%s\n", buf);
return 0;
}
We are closing the stdin (fd:0), Then how does dup(3) have the effect of reopening the stdin?

I think when you check the return value of these two call of dup, you will find the first is 3, and the second is 0. So before the second scanf function is called, file descriptor 0 is related with terminal again.

The documentation for dup says that it uses "the lowest-numbered unused file descriptor for the new descriptor." Since you just closed FD 0, the next time you use dup, 0 will be the lowest-numbered unused file descriptor, so that's where the duplicate FD will end up. And since you put back exactly what you got rid of, scanf is happy to work again after doing so.

Related

intricacies/understanding the stdio buffer and dup2

I am reading this lecture and found this following code sample which I modified to this:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
int main()
{
int fd;
char *s, *t;
off_t ret;
fd = open("file6", O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (dup2(fd, 1) < 0) { perror("dup2"); exit(1); }
printf("Standard output now goes to file6\n");
s = "before close\n";
write(1, s, strlen(s));
close(fd);
printf("It goes even after we closed file descriptor %d\n", fd);
printf("%ld\t"
"%ld\n",
(long int) lseek(fd,0,SEEK_CUR),
(long int) lseek(1,0,SEEK_CUR));
s = "And fwrite\n";
fwrite(s, sizeof(char), strlen(s), stdout);
printf("%ld\t"
"%ld\n",
(long int) lseek(fd,0,SEEK_CUR),
(long int) lseek(STDOUT_FILENO,0,SEEK_CUR));
fflush(stdout);
s = "And write\n";
write(1, s, strlen(s));
printf("after:\tAnd wri...: lseek(fd,0,SEEK_CUR)=%ld\t"
"lseek(STDOUT_FILENO,0,SEEK_CUR)=%ld\n",
(long int) lseek(fd,0,SEEK_CUR),
(long int) lseek(STDOUT_FILENO,0,SEEK_CUR));
return 0;
}
I am sharing two different outputs with the only change in the code being that the line fflush(stdout) is commented out in first and present in the second run.
Output (with fflush(stdout) commented):
before close
And write
Standard output now goes to file4
It goes even after we closed file descriptor 3
-1 13
And fwrite
-1 13
after: And wri...: lseek(fd,0,SEEK_CUR)=-1 lseek(STDOUT_FILENO,0,SEEK_CUR)=23
Output with flush(stdout) uncommented:
before close
Standard output now goes to file4
It goes even after we closed file descriptor 3
-1 13
And fwrite
-1 13
And write
after: And wri...: lseek(fd,0,SEEK_CUR)=-1 lseek(STDOUT_FILENO,0,SEEK_CUR)=127
I have two questions:
Why does "And write appears" first when fflush(stdout) is commented?
Why lseek prints -1 which I checked separately is an error message corresponding to errno ESPIPE. I am aware that lseek on terminal results in an error. But my current understanding is that since the standard output is dup2 to file6, then, this error shouldn't arise? Shouldn't it (lseek(STDOUT_FILENO, 0, SEEK_CUR)) simply return the current lseek pointer in file6, if dup2 is successful?
Why does "And write" appear first when fflush(stdout) is commented?
Because the C stdio buffers haven't filled, so nothing written using stdio APIs is actually sent to the output until the buffers fill, the stdio handle is flushed, or the program ends. Your direct write calls (e.g. for "And write") bypass stdio buffers entirely, and get written immediately, all the buffered stuff doesn't appear until the program ends (or at least, not until after "And write" has already been written).
Why lseek prints -1?
The first lseek was called on fd, which you closed shortly after dup2ing it over STDOUT_FILENO/1, so it fails. If you checked the errno properly (zeroing errno before each lseek, calling the two lseeks separately and storing or printing their errors and errnos separately, so one of them doesn't override the errno of the other before you even see it), you'd see it has a value corresponding to EBADF, not ESPIPE. The second lseek on (STDOUT_FILENO) works just fine. A mildly modified version of your code (using stderr so you can see the output for the last couple outputs even when you can't read the actual file, carefully zeroing errno each time, printing it before calling lseek again, and using strerror to show a friendly description of the errno) shows this clearly: Try it online!

How to use read function to read from terminal input (stdin)?

The main function will be in an infinite loop reading the numbers that the user puts in the terminal and storing them on a buffer. My problem is that I need to read from terminal using this function:
read(int fd, void *buf, size_t count);
How can the file descriptor point to the terminal?! (I hope I'm not saying some barbarity)
Thanks in advance!
EDIT: I already took your advice, but something is missing. This is a small program that I wrote to test:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int buffer[5], i=0, j;
int main(){
for(j=0; j<5; j++) buffer[j] = 0;
while(i<5){
read(STDIN_FILENO, buffer, 8);
printf("->%d\n", buffer[i]);
i++;
}
return 0;
}
Outup:
1
->2609
2
->0
3
->0
4
->0
5
->0
Why this doesn't print the numbers that I inserted?
Your process has three file descriptors open since it has been spawned: STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO (0, 1, 2 respectively).
These macros are defined in unistd.h.
read(STDIN_FILENO, buff, bytes)
By default the program's standard input is on file descriptor 0.
If you really mean to read from the terminal, instead of standard input, you can open() /dev/tty.
On POSIX you can use symbols like STDIN_FILENO to represents the input of your application. But beware that the standard input is not always the terminal, especially when you redirect input/output via the shell.
stdin is fd 0,
stdout is fd 1, and
stderr is fd 2.

Why system call read() does not work when using user input

It`s a file copying program.
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdlib.h>
int main()
{
int fd1,fd2, ndata;
char data[128];
char *openname[1], *creatname[1];
write(1, "Write open file name\n", 24); //user input
read(0, openname, 30 );
write(1, "Write creat file name\n", 25); //user input
read(0, creatname,30);
if((fd1 = open(openname, 0666))<0)
{
perror("cannot open the file");
exit(1);
}
if((fd2 = creat(creatname,0666))<0)
{
perror("cannot create the file");
exit(1);
}
while((ndata = read(fd1, data, 128))>0)
{
if((write(fd2, data, ndata))!=ndata)
{
perror("cannot write the file");
exit(1);
}
}
close(fd1);
close(fd2);
write(1, "File copy is done.",19);
return 0;
}
This code ain`t work. This code print the error message:
cannot open the file.
but if i change the code to this :
if((fd1 = open("copy.c", 0666))<0)
and this :
if((fd2 = creat("real.c",0666))<0)
worked well.
Why this error happend? Please answer.
Your declarations of openname and creatname are incorrect. They should be:
char openname[31], creatname[31];
read() does not add a null terminator to the input, you need to add it. read() returns the number of bytes read. So it should be:
int nread = read(0, openname, sizeof openname -1);
openname[nread-1] = '\0'; // subtract 1 to overwrite the newline
The type of openname and creatname is wrong, and gcc -Wall -g would have warned you. Declare e.g. char openname[256];
And you should use fgets(openname, sizeof(openname), stdin); to read it.
If you insist on using read, take care of the newline (if any) and add a zero terminating byte.
Learn also to use the gdb debugger.
read is very low level. In this case, it reads 30 bytes, including your enter key and also without a terminating null-byte. So the filename won't be what you think you've entered, it will contain additional garbage (and could even make your program crash due to the missing null-termination). You want to use fgets or readline instead.
In a nutshell, by using read() to input the file names, you are making this unnecessarily hard for yourself: it does not terminate the input with NUL, is not guaranteed to read the number of characters you expect, etc.
My advice would be to stick with scanf() or fgets().

How does stuff have to be ordered when using dup2 and pipe?

I am currently trying to understand combincation of dup2 and C pipes, but not even the simplest program seems to work. Already when reading example codes I am pretty confused on when they close ends of the pipe and where the output should be printed.
Sometimes the write end is closed, even though one line later output should be generated which should go into the pipe. In other examples, the unused end is closed (which makes more sense to me).
Then, I do not understand when dup2 should be executed. I guess it should become before the output I want to redirect, but I have the feeling I also saw that differently today.
So in the end I came up with this little test with printf and fflush in each line, where nothing gets redirected through the pipe. Why's that? What am I doing wrong?
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int out_pipe[2];
char *output[101];
if (pipe(out_pipe) != 0) {
perror("pipe()");
exit(1);
}
printf("Hello");
fflush(stdout);
dup2(out_pipe[1], STDOUT_FILENO);
printf("Hello");
fflush(stdout);
close(out_pipe[1]);
printf("Hello");
fflush(stdout);
read(out_pipe[0], output, 100);
close(out_pipe[0]);
printf("PIPE: %s", output);
fflush(stdout);
return 0;
}
End your printf() messages with newlines; the fflush() is still a good idea as you're about to change where standard output goes, though it's not usually necessary if the standard output of the program is going to a terminal. If the standard output was going to a file and the fflush() was not in place, then you'd get three copies of "Hello\n" written to the pipe.
When you change standard output to the pipe, your message is indeed written to the pipe.
When you close the write file descriptor, you don't run into any issues. You then write a second Hello to the pipe. You need this fflush() to ensure that the standard I/O package has actually written its buffered data to the pipe.
You then read from the pipe into the output buffer. You should check how many bytes you read since the string is not going to be null terminated. You should get 10 bytes read (when you don't have any newlines in the messages).
You then write to the pipe again with the PIPE: prefix.
To fix, write messages to standard error.
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int out_pipe[2];
char output[101];
if (pipe(out_pipe) != 0) {
perror("pipe()");
exit(1);
}
printf("Hello\n");
fflush(stdout);
dup2(out_pipe[1], STDOUT_FILENO);
printf("Hello\n");
fflush(stdout);
close(out_pipe[1]);
printf("Hello\n");
fflush(stdout);
int n = read(out_pipe[0], output, sizeof(output));
close(out_pipe[0]);
fprintf(stderr, "PIPE: %.*s\n", n, output);
return 0;
}
Note that I changed the definition of output from an array of char * to a simple array of char. With the changes, I got the output:
$ ./pipe3
Hello
PIPE: Hello
Hello
$
That's because I included newlines in the messages written to the pipe, as well as in the format string that ends up on standard error.
Is there a possibility to "reenable" stdout?
Yes; simply preserve a copy of the original file descriptor for standard output before using dup2(), and then reinstate the copy once you've done with the pipe.
I've removed the two leading fflush() calls, and the sample output demonstrates the difference between terminal and file output:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int out_pipe[2];
char output[101];
int old_stdout;
if (pipe(out_pipe) != 0) {
perror("pipe()");
exit(1);
}
printf("Hello\n");
old_stdout = dup(STDOUT_FILENO);
dup2(out_pipe[1], STDOUT_FILENO);
printf("Hello\n");
close(out_pipe[1]);
printf("Hello\n");
fflush(stdout);
int n = read(out_pipe[0], output, sizeof(output));
close(out_pipe[0]);
dup2(old_stdout, STDOUT_FILENO);
printf("PIPE: %d <<%.*s>>\n", n, n, output);
return 0;
}
Sample outputs:
$ ./pipe3Hello
PIPE: 12 <<Hello
Hello
>>
$./pipe3 > output
'pipe3' is up to date.
$ cat output
PIPE: 18 <<Hello
Hello
Hello
>>
$
If you remove the remaining fflush(), the program hangs. There is nothing in the pipe (because standard I/O hasn't flushed its buffer because it isn't full and the output isn't a terminal any more), but the pipe is open for writing, so the kernel considers that input could appear on it — if only the program that has the pipe open for writing wasn't waiting on the read end of the pipe for the input to appear. The program has deadlocked on itself.

C language. Read from stdout

I have some troubles with a library function.
I have to write some C code that uses a library function which prints on the screen its internal steps.
I am not interested to its return value, but only to printed steps.
So, I think I have to read from standard output and to copy read strings in a buffer.
I already tried fscanf and dup2 but I can't read from standard output. Please, could anyone help me?
An expanded version of the previous answer, without using files, and capturing stdout in a pipe, instead:
#include <stdio.h>
#include <unistd.h>
main()
{
int stdout_bk; //is fd for stdout backup
printf("this is before redirection\n");
stdout_bk = dup(fileno(stdout));
int pipefd[2];
pipe2(pipefd, 0); // O_NONBLOCK);
// What used to be stdout will now go to the pipe.
dup2(pipefd[1], fileno(stdout));
printf("this is printed much later!\n");
fflush(stdout);//flushall();
write(pipefd[1], "good-bye", 9); // null-terminated string!
close(pipefd[1]);
dup2(stdout_bk, fileno(stdout));//restore
printf("this is now\n");
char buf[101];
read(pipefd[0], buf, 100);
printf("got this from the pipe >>>%s<<<\n", buf);
}
Generates the following output:
this is before redirection
this is now
got this from the pipe >>>this is printed much later!
good-bye<<<
You should be able to open a pipe, dup the write end into stdout and then read from the read-end of the pipe, something like the following, with error checking:
int fds[2];
pipe(fds);
dup2(fds[1], stdout);
read(fds[0], buf, buf_sz);
FILE *fp;
int stdout_bk;//is fd for stdout backup
stdout_bk = dup(fileno(stdout));
fp=fopen("temp.txt","w");//file out, after read from file
dup2(fileno(fp), fileno(stdout));
/* ... */
fflush(stdout);//flushall();
fclose(fp);
dup2(stdout_bk, fileno(stdout));//restore
I'm assuming you meant the standard input. Another possible function is gets, use man gets to understand how it works (pretty simple). Please show your code and explain where you failed for a better answer.

Resources