1

for an exercise in a UNIX OS class I am supposed to develop a program with two sibling processes communicating through a pipe. One process (producer) is supposed to read strings from stdout and send them to the other process (consumer) which then has to convert the string to uppercase and print it again to stdout. The string "end" terminates both siblings and the parent process.

The program works fine however it breaks down when trying to read whole phrases from the user instead of single strings.

Here is a part of my main:

int main(void) {
int child_status, fd[2];

if (pipe(fd)) {
  fprintf(stderr, "Error: Could not create pipe.\n");
  return EXIT_FAILURE;
}

if (!fork()) {
  // child1
  producer(fd);
  exit(0);
} else if (!fork()) {
  // child2
  consumer(fd); 
  exit(0);
} else {
  // father
  pid_t pid;
...

The producer process:

void producer(int *fd) {
   char buff[BUFFSIZE];
   char *line;
 
   close(fd[0]);
   while (1) {
     fflush(stdout);
     fprintf(stdout, "Insert string:\t");
     scanf("%[^\n]s%*c", buff);
     fflush(stdout);
     line = strdup(buff);
     printToPipe(fd[1], line);
 
     if (!strcmp(line, "end")) // Special string terminates process
       return;
 
     sleep(1);
   }
 
   return;
 }

And the consumer process:

 void consumer(int *fd) {
   char *buff;
 
   close(fd[1]);
   while (1) {
     if ( (buff = readFromPipe(fd[0])) == NULL ) {
       close(fd[0]);
       return;
     }
     fprintf(stdout, "%s\n", strToUpper(buff));
     fflush(stdout);
   }
 
   return;
 }

The printToPipe() and readfromPipe() directly implement the read() and write() system calls.

Unexpected behaviour:

Once the first phrase is typed in and converted to upper case the program goes on an infinite loop printing out the prompt and the first uppercase phrase (i.e.: Insert string: TEST TEST). In particular:

  • subsequent scanfs are ignored despite me accounting for the \n character (scanf("%[^\n]s%*c", buff);). If i check for the scanf return value it is 0 for all iterations following the 1st one,
  • it seems like conusmer "self sustains" itself with it's output: it doesn't wait for new input through the pipe and keeps printing the transformed phrase.
Nepec
  • 67
  • 7
  • 3
    `"%[^\n]s%*c"` --> `"%[^\n]%*c"`. The `[...]` is a field directive in its own right, not a modifer for an `s` directive. The `s` in your format tries to match a literal 's' character. – John Bollinger Feb 21 '21 at 14:12
  • 1
    It that does not solve your problem then we probably need to see a [mre]. And note that in this case, preparing one would involve, in part, narrowing the problem to either the initial input or the transmission of the data over the pipe – John Bollinger Feb 21 '21 at 14:17
  • regarding: `scanf("%[^\n]s%*c", buff);` the input format specifier: `%[\n]` will leave the '\n' in the input stream. So the next character will NEVER be `s` Suggest: `scanf("%[^\n]%*c", buff);` – user3629249 Feb 22 '21 at 17:52
  • where is the definition of `printToPipe` and `readFromPipe` ??? – Luis Colorado Feb 22 '21 at 20:14

1 Answers1

2

subsequent scanfs are ignored despite me accounting for the \n character

Code never reads a '\n'

scanf("%[^\n]s%*c", buff); is a problem @John Bollinger.

  1. "%[^\n]" reads an unlimited number of non-'\n characters into buff, possibly overflowing buff. It is worse than gets(). If at least 1 non-'\n' character read, a null character is appended to buff, else scanning stops.

  2. "s" matches an 's' which is not expected after the above - only a '\n', so scanning stops without consuming any '\n'.

  3. Rest of the the format is never executed.

Code never checks the return value of input functions

With checking the return value of scanf(), the contents of buff may be unchanged or indeterminant.

// scanf("%[^\n]s%*c", buff);
if (scanf("%[^\n]s%*c", buff) == 1) {
  ; // OK to use `buff`
}

OP's scanf() may consume most of the first line, leaving the '\n' in stdin, but the 2nd scanf() call will certainly stop right away as it attempts to read the '\n' of the first line. This likely leaves buff unchanged.

Use fgets() to read a line of input from stdin and save as a string. @xing

// scanf("%[^\n]s%*c", buff);
if (fgets(buff, sizeof buff, stdin) == NULL) {
  ; // Handle end-of-file or rare input error
  break;
}

To remove a '\n' from an input line, see this answer and others.
Code could leave the '\n' in the string and not append one when printing.

//fprintf(stdout, "%s\n", strToUpper(buff));
fprintf(stdout, "%s", strToUpper(buff));

This will better handle long lines of input that exceed BUFFSIZE as excess characters are simply left for the next iteration.

OP may need to adjust strcmp(line, "end") test to account for possible trailing '\n'.


One process (producer) is supposed to read strings from stdout ....

Detail: producer is not reading a string from stdout, but reading a line from stdin into a string.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • Ultimately I obtained the expected behavior by using `scanf("%[^\n]%*c);`, I was indeed misunderstanding the use of the field directive. I have then substituted the scanf with a more secure fgets as you and @xing suggested which works like a charm. Thank you very much! – Nepec Feb 21 '21 at 15:48
  • 1
    @Nepec Note: `scanf("%[^\n]%*c);` is bad like [gets](https://stackoverflow.com/q/1694036/2410359) as it has not input limit, nor checks the function return. Best to avoid and good you have moved to `fgets()`. – chux - Reinstate Monica Feb 21 '21 at 15:51