Search code examples
cpipeipc

C pipe to write / read sequence of doubles fails


For this C project I am trying to use a pipe to get a parent process to communicate with a child. The child is supposed to read lines (one each second) from a txt file (containing real numbers) and use a pipe to supply the parent, which in turn should read from the pipe and write the numbers into a log file. However the parent process only reads a sequence of 0.000000. Here is my code:

#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <string.h>
#include <time.h>
#include <wait.h>
#define MAX_I 5
#define delay 1
void read_input(FILE *fp);

FILE *file_log;
int status;
pid_t pid;
int pipeFD[2];

int main(int argc, char **argv) {
//initialize
    FILE *fp;

    if (argc != 2){
    if ((fp = fopen("input.txt", "r")) == NULL) {
        printf("Error! opening file");
        exit(1);}
    }
    else{
        fp = fopen(argv[1], "r");
        }

    if (pipe(pipeFD) == -1){    /* creates a pipe */
    fprintf(stderr, "\nERROR: pipe() failed\n");
    exit(1);}

    file_log=fopen("file.log","w+"); /*open the log file*/

    pid=fork();
    if(pid==0) {//child process
    printf ("%d starts \n", getpid());
    close(pipeFD[0]);//close read end for child
    read_input(fp);
    return 0;
    exit(status);
    }else{//parent 
    double speed_read;
    close(pipeFD[1]);//close write end for parent
    while(1){
        if (read(pipeFD[0], &speed_read, sizeof(speed_read)) >0){
        if (speed_read<0)
        break;
        fprintf(file_log, "%f \n", speed_read); 
        printf("process %d received %f from child \n",getpid(),speed_read);
        }else
            printf("Nothing there to read \n");  
        }
    printf("parent ended \n");
    wait(&status);
    fclose(fp);
    fclose(file_log);
    }
    return 0;
}

void read_input(FILE *fp){
    char *line = NULL;
    double speed;
    int i=0; size_t len = 0; double exit_sign =-10.0;
    while(getline(&line, &len, fp) != -1) { 
        speed=atof(line);
        i++;
        if(i>MAX_I){//reads up to MAX_I rows of input
                        printf("I'll send the exit sign to parent now\n");
                        write(pipeFD[1], &exit_sign, sizeof(double));
                        free(line);
                        break;      
                    }
        if(write(pipeFD[1], &speed, sizeof(double)>0)){
            printf("%d at %d wrote that speed is %f\n",getpid(), i,speed);
        }else{printf("Write on pipe failed\n");}
        sleep(delay);
        }
    free(line);
}

Here is what is printed:


15032 starts

15032 at 1 wrote that speed is 0.345670

process 15031 received 0.000000 from child

15032 at 2 wrote that speed is 12.678890

process 15031 received 0.000000 from child

15032 at 3 wrote that speed is 34.789870

process 15031 received 0.000000 from child

15032 at 4 wrote that speed is 0.000000

process 15031 received 0.000000 from child

15032 at 5 wrote that speed is 12.009288

process 15031 received 0.000000 from child

I'll send the exit sign to parent now

parent ended


Likewise the log file consists in the following:

0.000000

0.000000

0.000000

0.000000

0.000000


Solution

  • in read_input the line :

    if(write(pipeFD[1], &speed, sizeof(double)>0)){
    

    must be

    if(write(pipeFD[1], &speed, sizeof(double))>0){
    

    in your case you do not write sizeof(double) bytes but only 1

    Note read_input does two times free(line); with an undefined behavior, you must remove the one when if(i>MAX_I){

    After the corrections, compilation and execution :

    pi@raspberrypi:/tmp $ gcc -g -Wall p.c
    pi@raspberrypi:/tmp $ cat input.txt 
    1.2
    2.3
    3.45
    7.8
    9.12
    12.345
    pi@raspberrypi:/tmp $ ./a.out
    15121 starts 
    15121 at 1 wrote that speed is 1.200000
    process 15120 received 1.200000 from child 
    15121 at 2 wrote that speed is 2.300000
    process 15120 received 2.300000 from child 
    15121 at 3 wrote that speed is 3.450000
    process 15120 received 3.450000 from child 
    15121 at 4 wrote that speed is 7.800000
    process 15120 received 7.800000 from child 
    15121 at 5 wrote that speed is 9.120000
    process 15120 received 9.120000 from child 
    I'll send the exit sign to parent now
    parent ended 
    pi@raspberrypi:/tmp $ cat file.log
    1.200000 
    2.300000 
    3.450000 
    7.800000 
    9.120000 
    pi@raspberrypi:/tmp $ 
    

    Out of that when you detect the EOF and do printf("Nothing there to read \n"); to also do a break; to finish the while seems better. Note you can also send exit_sign when you reach the end of the input file in read_input. If you don't the parent never ends writing Nothing there to read if the input file has less than MAX_I lines


    A pipe is a stream but reading or writing pipe data is atomic if the size of data written is not greater than PIPE_BUF where PIPE_BUF is at least 512 bytes, and 4096 under Linux

    So in your case where in main you read as fast as possible and the read_input function sleeps, while you read and write the same small amount of bytes, you have no risk. Else as in general when you read in a stream you need to loop until you get the expected amount of bytes, for instance replacing the use of read by a call to a function like :

    int read_block(int f, char * p, size_t sz)
    {
      ssize_t n;
    
      while ((n = read(f, p, sz)) > 0) {
        if ((sz -= (size_t) n) == 0)
          return 1;
        p += n;
      }
    
      return -1;
    }