C:Workshop:2018:D3

De wiki-prog
Aller à : navigation, rechercher


Introduction

The purpose of this session is to build a complete program combining process management, pipe and redirection.

This subject describes the expected behavior of your program and then give you some advice on how you should organize it.

The program

We want to build a program that reads lines from a file, sends each line to another program (provided as parameter on the command line) through a pipe and, through another pipe, read its respond and display it.

Here is an example of a session, code for the program worker is provided later in this subject:

shell> cat test01 
First line.
Second line.
shell> ./task_mgr ./worker test01 
>>> Result: 4228686178 <<<
>>> Result: 3718716647 <<<
shell>

Expected behavior

The program task_mgr takes two parameter:

  • a program (worker) whose behavior is described later
  • a text file (input)

task_mgr will open two pipes in order to communicate with worker, read end of the first one will replace the standard input of worker and the write end of the second one will replace the standard output of worker.

W-D3-schema.png

Once pipes are ready, task_mgr will fork and, in its child, executes worker (with the correct redirections.) After launching worker (fork and exec), the main program (parent of the fork) will start reading input line by line.

For each line of input, task_mgr will send the bytes of the line (including the final '\n') followed by two null bytes. task_mgr use the first pipe for sending bytes and then read on the other pipe the response from worker. The result from worker is a simple unsigned integer written as a block of bytes on the pipe. Once result is arrived, task_mgr displays it (see example above.)

When input is finished (no more lines), task_mgr will close the first pipe, and thus sends a end-of-file to worker, then it'll wait for the termination of worker and finally close remaining pipe and files.

Worker's code

Here is a simple program that have the expected worker behavior, you can run this code alone to see how it works:

# define _XOPEN_SOURCE 700
 
# include <err.h>
# include <errno.h>
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <unistd.h>
 
static
unsigned compute_hash(const unsigned char *data, size_t len) {
  unsigned h = 0;
  for (size_t i = 0; i < len; i++) {
    unsigned ho = h & 0xf8000000;
    h <<= 5;
    h ^= (ho >> 27);
    h ^= (unsigned)(data[i]);
  }
  return h;
}
 
static
void read_data(int request_fd, int result_fd) {
  size_t capa = 32, len = 0;
  unsigned char *data = malloc(capa * sizeof (unsigned char));
  memset(data, 0xff, capa);
  int r;
  while ( (r = read(request_fd, data + len, capa - len)) ) {
    if (r == -1) {
      if (errno == EINTR) continue;
      err(3, "Can't read data");
    }
    len += r;
    if (len > 2 && data[len - 2] == 0 && data[len - 1] == 0) {
      // Message end
      unsigned result = compute_hash(data, len - 2);
      len = 0;
      write(result_fd, &result, sizeof (unsigned));
      memset(data, 0xff, capa);
      continue;
    }
    if (len >= capa) {
      capa *= 2;
      data = realloc(data, capa * sizeof (unsigned char));
    }
  }
  free(data);
}
 
int main() {
  read_data(STDIN_FILENO, STDOUT_FILENO);
  return 0;
}

Organization

In order to organize your code, we give you some advices.

The idea is to split your code in functions in order to avoid problems. Here is a recommended split:

  • A function sending data to the pipe, this function should take the output file-descriptor, the data buffer and its length.
  • A function reading and printing the result through the second pipe.
  • A function reading lines and running the 2 previous functions foreach line.
  • A main function creating the 2 pipes and running worker using fork/exec.