0

I've just started to learn Fifos in C and I've written the following program to test their funcionalities:

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


void error(){
    printf("Error occured: %s\n", strerror(errno));
    exit(EXIT_FAILURE);
}

int main (int argc, char* argv[]){
    const char fifoName[] = "test";
    unlink(fifoName);
    if(mkfifo(fifoName, 0700) < 0)
        error();
    pid_t pid ;
    if((pid=fork()) < 0)
        error();

    if (pid == 0){
        FILE *f = fopen(fifoName, "r");
        puts("\nchild started writing\n");
        for(size_t i = 0; i < 10 ; i++){
            fputc(i + 48, f);
            fputc(i+48, stdout);
        }
        puts("\nchild finished writing\n");
        fclose(f);
    }
    else{
        FILE *f = fopen(fifoName, "w");
        puts("parent started reading");
        while(!feof(f)){
            char c = fgetc(f);
            if(c != EOF)
                putc(c, stdout);
        }
        puts("parent finised reading");
        fclose(f);

    }


    unlink(fifoName);
    return 0;
}

The problem is that the parent process (the reader) doesn't terminate, on the contrary, it keeps reading EOF chars even after the child has finished writing correctly. As you can see I decided to use FILE* streams, opening them in read and write mode depending on the process. By doing so I have the standard functions fprintf, fscanf etc and overall I have the buffered-derived performance of the streams. Could this implementation be the reason behind the problematic behaviour?

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Fiabio
  • 11
  • 1
  • 2
    You open the FIFO for reading and try to write to it; you open it for writing and try to read from it. Not good! – Jonathan Leffler Feb 25 '22 at 13:36
  • Note that you are using `feof` incorrectly: https://stackoverflow.com/questions/5431941/why-is-while-feof-file-always-wrong – William Pursell Feb 25 '22 at 21:29
  • The parent does not "keep reading EOF chars". EOF is not a value that is read from the stream. Also, the value returned by `fgetc` is an `int`, not a `char`, so `char c = fgetc(f);` is an error. That needs to be `int c = fgetc(f);` – William Pursell Feb 25 '22 at 21:40

1 Answers1

1

As I noted in a comment, you have the read/write modes on the fopen() calls backwards — you try to write to the file stream you open for reading and try to read from the stream you open for writing.

You should also note while (!feof(file)) is always wrong. Actually, the loop should be:

int c;
while ((c = fgetc(f)) != EOF)
    putchar(c);  // or putc(c, stdout);

Note that the return value from fgetc() is an int, not char; that's because the function can return any char value and also one distinctive value, EOF.

This code works:

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>   /* mkfifo() */
#include <unistd.h>

static void error(void)
{
    printf("Error occurred: %s\n", strerror(errno));
    exit(EXIT_FAILURE);
}

int main(void)
{
    const char fifoName[] = "test";
    unlink(fifoName);
    if (mkfifo(fifoName, 0700) < 0)
        error();
    pid_t pid;
    if ((pid = fork()) < 0)
        error();

    if (pid == 0)
    {
        FILE *f = fopen(fifoName, "w");
        puts("\nchild started writing");
        for (size_t i = 0; i < 10; i++)
        {
            fputc(i + '0', f);
            fputc(i + '0', stdout);
        }
        puts("\nchild finished writing\n");
        fclose(f);
    }
    else
    {
        FILE *f = fopen(fifoName, "r");
        puts("parent started reading");
        int c;
        while ((c = fgetc(f)) != EOF)
            putc(c, stdout);
        puts("\nparent finished reading");
        fclose(f);
    }

    unlink(fifoName);
    return 0;
}

Output:

child started writing
0123456789
child finished writing

parent started reading
0123456789
parent finished reading

The output sequence above was the most common one I saw, but is not guaranteed — sometimes the parent's output is partially interleaved with the child's output.

It would be worth upgrading the error() function so it takes a string which you can use to identify where the error occurred and so that it writes to stderr (standard error stream) rather than stdout:

static void error(const char *tag)
{
    fprintf(stderr, "%s: error %d (%s)\n", tag, errno, strerror(errno));
    exit(EXIT_FAILURE);
}

A more general version of the function would prefix the error message with the program name, which would be derived from argv[0]. (I removed the arguments to main() since they were unused and my default compiling options reject code defining unused variables.). You can find my (moderately complex) version of similar error reporting routines in my SOQ (Stack Overflow Questions) repository on GitHub as files stderr.c and stderr.h in the src/libsoq sub-directory. You can find analogous code as err(3), available on at least BSD (macOS) and Linux.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • Thanks a lot, while the read/write modes in the fopen were a distraction error I actually didn't know about the loop condition being wrong. I still have a small doubt: the parent process starts to read and if there's not a new character to be read from the fifo it waits for a new write from the child, correct? Also, could the use of `fprintf()` and `fscanf()` functions generate problems? Why should one prefer `open()`, `read()` and `write()` S.C. withouth involving libraries/streams? Thanks again for the exhaustive answer! – Fiabio Feb 26 '22 at 14:28
  • Given the way the standard I/O library works, it is virtually certain that what the child writes one character at a time to the buffer is buffered up and only sent when the file stream is closed by the child. The parent then gets the the data one character at a time. You'd have to use `fflush()` in the loop to send the characters one at a time. You could experiment with `fflush()` and `sleep()` in the child, but the parent still only gets the input 'all at once'. You have to unbuffer the parent's stream (`setvbuf(f, 0, _IONBF, 0):`) to get the characters relayed one at a time. – Jonathan Leffler Feb 26 '22 at 15:32
  • Using standard I/O functions doesn't cause trouble as long as you understand how they behave. Using file descriptor I/O avoids the buffering inherent in using file streams in default modes. It depends on what you're doing as to whether that is a good thing or not. Both have their uses. – Jonathan Leffler Feb 26 '22 at 15:38