2

I'm trying to read a text file's string from a process, then deliver the string to another process via named pipes on LINUX. The problem is when i type './reader text.txt = receiver' to the console the recieving process' read() function returns an error if i put the line

fcntl(fd, F_SETFL, O_NONBLOCK);

or gets stuck on read() function if i remove it.

heres the process that reads the string (reader)

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

 int main(int argc,char *argv1[]){

 if(argc==4 && strcmp(argv1[2],"=") == 0){

 char mesaj[99999]; //message to be delivered

    char line[150];
    FILE *fp =fopen(argv1[1],"r");  //reading from a text file

    if(fp==NULL){

        printf("text file error!");
        exit(1);
    }


    while(fgets(line,sizeof(line),fp)){
        strcat(mesaj,line); //append every line to message

    }

    fclose(fp);

    mesaj[strlen(mesaj)-1]='\0';

    int n =strlen(mesaj)+1;


   //printf("got the text  %s\n",mesaj);


    if(mkfifo("myFifo",0777)== -1 && errno!= EEXIST){ 
        printf("Pipe error");
        exit(1);
    }


    printf("opening\n");
    int fd= open("myFifo",O_RDWR);
    if(fd==-1){
        printf("open error");
        exit(1);
    }
    printf("opened");


    if( write(fd, mesaj,sizeof(char)*n)==-1){
        printf("write error");
        exit(1);
    }


    printf("written");

    close(fd);

    printf("closed");
    fflush(stdout);


   char mesajSizeChar[n];
   sprintf(mesajSizeChar, "%d", n);


    char *args[]={mesajSizeChar,NULL}; //send the message size as parameter for the other process
    char program[]="./";
    strcat(program,argv1[3]); // recieved process name as parameter
    execv(program,args); // call the other process
    perror("execv");


    return 0;
     }
 }

and heres the recieving process (reciever)

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

int main(int argc,char *argv1[]){

int mesajSize=atoi(argv1[0]); //convert message size to integer
char mesaj[99999];

printf("\ncame here\n");


int fd= open("myFifo",O_RDWR);
fcntl(fd, F_SETFL, O_NONBLOCK);
printf("\nopen \n");

if(fd==-1)
    printf("pipe error\n");


if(read(fd,mesaj,sizeof(char)*mesajSize)==-1)
printf("read error\n");


printf("read \n");
printf("\nworked: %s \n",mesaj);

close(fd);
return 0;

 }
berkeo
  • 33
  • 1
  • 9
  • If you use non-blocking mode, you have to check for the error `EWOULDBLOCK`. That's not really an error, it just means there's nothing available to read. – Barmar Jun 12 '20 at 20:14
  • 1
    The reader should open the FIFO in `O_WRONLY` mode, the receiver should open it in `O_RDONLY` mode. – Barmar Jun 12 '20 at 20:16
  • 1
    You need to initialize `mesaj` to an empty string before you can call `strcat()` on it: `char mesaj[99999] = "";` – Barmar Jun 12 '20 at 20:19
  • @Barmar yes but that way it gets suck again waiting for other end to open and i can't do that because execv(); does not work like fork(). execv() replaces the current process with the other. if i call execv() first i cant use write() after, and if i open the pipe first, then it gets stuck so it wont execute execv() – berkeo Jun 12 '20 at 20:21
  • How big is the file? You may be running into the limit of the pipe buffer size. – Barmar Jun 12 '20 at 20:27
  • If you try to write too much to a pipe, you have to wait for another process to read to make room. If you're not starting the receiving process until after you write everything, you have a deadlock. – Barmar Jun 12 '20 at 20:28
  • This is a very strange use of a pipe, usually the purpose is to be able to run the writer and reader concurrently, not sequentially. – Barmar Jun 12 '20 at 20:29
  • it's 25 lines long, i tried shorter version of the text file still doesnt work – berkeo Jun 12 '20 at 20:29
  • 1
    OT: for ease of readability and understanding (the compiler does not care) 1) please consistently indent the code. Indent after every opening brace '{'. Unindent before every closing brace '}'. Suggest each indent level be 4 spaces – user3629249 Jun 13 '20 at 17:28
  • 1
    OT: regarding: `execv(program,args); // call the other process perror("execv"); return 0;` normally, a 0 returned value indicates success, but this return indicates a failure. Suggest: `exit( EXIT_FAILURE );` Note: `exit()` and EXIT_FAILURE are exposed via: `#include ` – user3629249 Jun 13 '20 at 17:34
  • 1
    OT: regarding: `printf("read error\n");` Error messages are to be output to `stderr`, not `stdout`. When the error indication is from a C library function, then should also output the text reason the system thinks the error occurred. The function: `perror( "read error" );` is made to perform these two activities. – user3629249 Jun 13 '20 at 17:38
  • 1
    regarding: `printf("read error\n"); printf("read \n"); printf("\nworked: %s \n",mesaj);` Since the `read()` failed, the code should not be outputting a statement that it was successful. Suggest, after the output of the error message to 'cleanup' and insert the statement: `exit( EXIT_FAILURE );` And use braces '{' '}' around the error handling – user3629249 Jun 13 '20 at 17:41
  • 1
    when starting a process (for instance via `execv()`) set a valid array of `argv[]` to pass to that function. I.E. something similar to: `char *args[] = ( program, argv1+2, NULL }; – user3629249 Jun 13 '20 at 17:50
  • 1
    regarding: `FILE *fp =fopen(argv1[1],"r"); ` before accessing beyond `argv[0]`, always check `argc` to assure the expected command line parameters were actually entered by the user. When the right number of command line parameters was not entered, then output, to `stderr`, a USAGE statement, similar to: `fprintf( stderr, "USAGE %s inputFile '=' childProcessName\n", argv[0] );` followed by: `exit( EXIT_FAILURE );` – user3629249 Jun 13 '20 at 17:56
  • 1
    OT: regarding: `if(read(fd,mesaj,sizeof(char)*mesajSize)==-1)` 1) The expression: `sizeof(char)` is defined in the C standard as 1. Multiplying anything by 1 has not effect and just clutters the code. Suggest removing that expression. 2) the function: `read()` is expecting the third parameter to have type `size_t` not `int` – user3629249 Jun 13 '20 at 18:08
  • 1
    regarding: `if(fd==-1) printf("pipe error\n");` 1) this should be immediately after the call to `open()` I.E. while the value in `errno` is still valid) and should be written as: `if( fd == -1 ) perror( "open FIFO for read/write failed" );` Notice the use of appropriate horizontal spacing for readability – user3629249 Jun 13 '20 at 18:12
  • 1
    regarding: `if(read(fd,mesaj,sizeof(char)*mesajSize)==-1)` and `fcntl(fd, F_SETFL, O_NONBLOCK);` What do you think will happen with these statements if the prior call to `open()` failed? – user3629249 Jun 13 '20 at 18:14
  • 1
    OT: it is a poor programming practice to include header files those contents are not used.. In the child process, these contents of these files are not used: `string.h` and `sys/types.h` and `sys/stat.h` and `errno.h` – user3629249 Jun 13 '20 at 18:21
  • @user3629249 honestly i was experimenting some codes, some are codes leftover from experiments. because i'm just learning pipes and stuff. i have fixed some of the problems you have mentioned after i fixed the main issue. This is not the final code. However thank you so much for your suggestions. – berkeo Jun 13 '20 at 18:37

2 Answers2

3

The problem is that you closed the pipe in the first process. A pipe doesn't have any permanent storage, it can only hold data while it's open by at least one process. When you close the pipe, the data that you've written to it is discarded.

As a result, when the second process tries to read from the pipe, there's nothing available.

You need to keep the pipe FD open when you execute the second process. Get rid of close(fd); in the reader program.

Barmar
  • 741,623
  • 53
  • 500
  • 612
3

To use a FIFO or pipe, sender and receiver must run concurrently, but you are trying to run them sequentially. A FIFO or pipe has no persistent storage, so the system does not allow you to write to one unless unless at least one process has the read end open, so as to be able to read it.

Ordinarily, attempts to open a FIFO for writing will block while there are no readers, and vice versa. Your reader is working around this by opening the FIFO for both reading and writing, even though it intends only to write. You will find that if it tries to send too much data to the FIFO then it blocks, because nothing is reading the data, and pipes / FIFOs have limited buffer capacity. When it closes the FIFO's fd, leaving no process with it open, all data previously written to it are lost.

Your receiver also erroneously opens the FIFO for both reading and writing, whereas it should open it only for reading. There being no data to read from it, I would expect attempts to read from it to block indefinitely, unless you put it into non-blocking mode. This seems to be exactly what you describe.

To fix it, I would suggest

  1. taking the code that starts the receiver out of the reader. Instead, start the reader and receiver separately. Alternatively, the reader may start out by fork()ing, with the resulting child process execv()ing the receiver.

  2. The reader should open the FIFO with flag O_WRONLY, and the receiver should open it with mode O_RDONLY.

  3. You should find a different way to convey the message length from reader to receiver, or, better, to avoid needing to tell it the message length in advance at all. You could, for instance, send an initial fixed-length message that conveys the length of the main message data, but more typical would be for the receiver to just keep reading data until it sees EOF.

  4. The reader will cause the receiver to see EOF on the FIFO by closing it, either explicitly or by terminating. This depends on the receiver having it open in read-only mode, however, and there being no other writers.

  5. The reader probably should not attempt to buffer the whole message in memory at once. It should not, in any case, assume that a write() call will transfer the full number of bytes requested -- the return value will tell you how many actually were transferred. You need to be prepared to use multiple write() calls in a loop to transfer all the data.

  6. Similarly, the receiver cannot rely on a single read() call to transfer the full number of bytes requested in one call, even if it has some way to know how many are coming. As with write(), you need to be prepared to use multiple read()s to transfer all the data.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157