3

I am a C beginner, trying to use dup(), I wrote a program to test this function, the result is a little different from what I expected.

Code:

// unistd.h, dup() test

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

extern void dup_test();

int main() {
    dup_test();
}

// dup()test
void dup_test() {
    // open a file
    FILE *f = fopen("/tmp/a.txt", "w+");
    int fd = fileno(f);
    printf("original file descriptor:\t%d\n",fd);

    // duplicate file descriptor of an opened file,
    int fd_dup = dup(fd);
    printf("duplicated file descriptor:\t%d\n",fd_dup);
    FILE *f_dup = fdopen(fd_dup, "w+");

    // write to file, use the duplicated file descriptor,
    fputs("hello\n", f_dup);
    fflush(f_dup);
    // close duplicated file descriptor,
    fclose(f_dup);
    close(fd_dup);

    // allocate memory
    int maxSize = 1024; // 1 kb
    char *buf = malloc(maxSize);

    // move to beginning of file,
    rewind(f);
    // read from file, use the original file descriptor,
    fgets(buf, maxSize, f);
    printf("%s", buf);

    // close original file descriptor,
    fclose(f);

    // free memory
    free(buf);
}

The program try write via the duplicated fd, then close the duplicated fd, then try to read via the original fd.

I expected that when I close the duplicated fd, the io cache will be flushed automatically, but it's not, if I remove the fflush() function in the code, the original fd won't be able to read the content written by the duplicated fd which is already closed.

My question is:

Does this means when close the duplicated fd, it won't do flush automatically?


@Edit:

I am sorry, my mistake, I found the reason, in my initial program it has:

close(fd_dup);

but don't have:

fclose(f_dup);

after use fclose(f_dup); to replace close(f_dup); it works.

So, the duplicated fd do automatically flush if close in a proper way, write() & close() is a pair, fwrite() & fclose() is a pair, should not mix them.

Actually, in the code I could have use the duplicated fd_dup directly with write() & close(), and there is no need to create a new FILE at all.

So, the code could simply be:

// unistd.h, dup() test

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

#define BUF_SIZE 1024 // 1 kb

extern void dup_test();

int main() {
    dup_test();
}

// dup()test
void dup_test() {
    // open a file
    FILE *f = fopen("/tmp/a.txt", "w+");
    int fd = fileno(f);
    printf("original file descriptor:\t%d\n",fd);

    // duplicate file descriptor of an opened file,
    int fd_dup = dup(fd);
    printf("duplicated file descriptor:\t%d\n",fd_dup);

    // write to file, use the duplicated file descriptor,
    write(fd_dup, "hello\n", BUF_SIZE);
    // close duplicated file descriptor,
    close(fd_dup);

    // allocate memory
    char *buf = malloc(BUF_SIZE);

    // move to beginning of file,
    rewind(f);
    // read from file, use the original file descriptor,
    fgets(buf, BUF_SIZE, f);
    printf("%s", buf);

    // close original file descriptor,
    fclose(f);

    // free memory
    free(buf);
}
Eric
  • 22,183
  • 20
  • 145
  • 196
  • 2
    You might want to `strace` your program to check what syscalls it actually does. On my system your program always prints `hello`. I think what might be happening is `f_dup` being flushed, but `f` making view of your file before that (at `fopen`). – zch Nov 03 '14 at 13:55
  • since you have `fdopen` your `fd`, the object you should mainly operate on is `FILE*`. they are essentially the same thing, don't close the same thing twice. and, you are kind of confused about `FILE*` and `int` which is meant to be `fd`. `FILE *` is file stream, it's a standard representation, so it's cross platformed. `fd` is actually `int`, a index number of file descriptor table, it's the feature of *nix system. – Jason Hu Nov 03 '14 at 14:05
  • @zch Thx, strace is a great tool! – Eric Nov 03 '14 at 14:09

2 Answers2

2

From dup man pages:

After a successful return from one of these system calls, the old and new file descriptors maybe used interchangeably. They refer to the same open file description (see open(2))and thus share file offset and file status flags; for example, if the file offset is modified by using lseek(2) on one of the descriptors, the offset is also changed for the other.

It means the seek pointer is changed when you write to the duplicated file descriptor, so, reading from the first file descriptor after writing to the duplication shouldn't read any data.

You are using fdopen to create separated seek_ptr and end_ptr of the duplicated stream, in that way, the fd_dup stops being a duplication. That's why you can read data after flushing and closing the stream.

I couldn't find any strong facts about why you can't read if you don't flush the second file descriptor. I can add that it may be related to sync system call.

After all, if you need a IO buffer, you might be using the wrong mechanism, check named pipes and other buffering OS mechanism.

Felipe Lavratti
  • 2,887
  • 16
  • 34
  • I did rewind() after write before read, so seek pointer is not a problem. Anyhow, thanks for your suggestion, the code is just try to figure out the feature of dup(), in a real world program might use different approach. – Eric Nov 03 '14 at 14:04
1

I cannot really understand your problem. I tested it under Microsoft VC2008 (had to replace unistd.h with io.h) and gcc 4.2.1.

I commented out fflush(f_dup) because it is no use before a close and close(fd_dup); because the file descriptor was already closed, so the piece of code now looks like :

// write to file, use the duplicated file descriptor,
fputs("hello\n", f_dup);
// fflush(f_dup);
// close duplicated file descriptor,
fclose(f_dup);
// close(fd_dup);

And it works correctly. I get on both systems :

original file descriptor:       3
duplicated file descriptor:     4
hello
Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252