giggiox
giggiox

Reputation: 65

is it possible to read and write with the same file descriptor in C

I am trying to write to a file and display the output of the thing i wrote with another process. The code i come up with:

void readLine (int fd, char *str) {
    int n;
    do { 
        n = read (fd, str, 1);
    } while (*str++ != '\0');
}

int main(int argc,char ** argv){
    int fd=open("sharedFile",O_CREAT|O_RDWR|O_TRUNC,0600);
    if(fork()==0){
        char buf[1000];
        while(1) {
            readLine(fd,buf);
            printf("%s\n",buf); 
        }
    }else{
        while(1){
             sleep(1);
             write(fd,"abcd",strlen("abcd")+1);
        }
    }
}

the output i want (each result spaced from the other with a period of one second):

abcd
abcd
abcd
....

Unfortunately this code doesn't work, it seems that the child process (the reader of the file "sharedFile") reads junk from the file because somehow it reads values even when the file is empty. When trying to debug the code, readLine function never reads the written file correctly,it always reads 0 bytes. Can someone help?

Upvotes: 1

Views: 3628

Answers (1)

Nate Eldredge
Nate Eldredge

Reputation: 58805

First of all, when a file descriptor becomes shared after forking, both the parent and child are pointing to the same open file description, which means in particular that they share the same file position. This is explained in the fork() man page.

So whenever the parent writes, the position is updated to the end of the file, and thus the child is always attempting to read at the end of the file, where there's no data. That's why read() returns 0, just as normal when you hit the end of a file.

(When this happens, you should not attempt to do anything with the data in the buffer. It's not that you're "reading junk", it's that you're not reading anything but are then pretending that whatever junk was in the buffer is what you just read. In particular your code utterly disregards the return value from read(), which is how you're supposed to tell what you actually read.)

If you want the child to have an independent file position, then the child needs to open() the file separately for itself and get a new fd pointing to a new file description.

But still, when the child has read all the data that's currently in the file, read() will again return 0; it won't wait around for the parent to write some more. The fact that some other process has a file open for writing don't affect the semantics of read() on a regular file.

So what you'll need to do instead is that when read() returns 0, you manually sleep for a while and then try again. When there's more data in the file, read() will return a positive number, and you can then process the data you read. Or, there are more elegant but more complicated approaches using system-specific APIs like Linux's inotify, which can sleep until a file's contents change. You may be familiar with tail -f, which uses some combination of these approaches on different systems.

Another dangerous bug is that if someone else writes text to the file that doesn't contain a null byte where expected, your child will read more data than the buffer can fit, thus overrunning it. This can be an exploitable security vulnerability.

Here is a version of the code that fixes these bugs and works for me:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

void readLine (int fd, char *str, size_t max) {
    size_t pos = 0;
    while (pos < max) {
        ssize_t n = read(fd, str + pos, 1);
        if (n == 0) {
            sleep(1);
        } else if (n == 1) {
            if (str[pos] == '\0') {
                return;
            }
            pos++;
        } else {
            perror("read() failure");
            exit(2);
        }
    }
    fprintf(stderr, "Didn't receive null terminator in time\n");
    exit(2);
}

int main(int argc, char ** argv){
    int fd=open("sharedFile", O_CREAT|O_RDWR|O_TRUNC, 0600);
    if (fd < 0) {
        perror("parent opening sharedFile");
        exit(2);
    }
    pid_t pid = fork();
    if (pid == 0){
        int newfd = open("sharedFile", O_RDONLY);
        if (newfd < 0) {
            perror("child opening sharedFile");
            exit(2);
        }
        char buf[1000];
        while (1) {
            readLine(newfd, buf, 1000);
            printf("%s\n",buf); 
        }
    } else if (pid > 0) {
        while (1){
            sleep(1);
            write(fd,"abcd",strlen("abcd")+1);
        }
    } else {
        perror("fork");
        exit(2);
    }
    return 0;
}

Upvotes: 4

Related Questions