Question 4 (20 points, 45 minutes)

Save this question for last.

Behold the definition of doserver. Assume that serverprog is an interactive program that takes input on standard input and output on standard output.

doserver(int connection)
{
  dup2(connection, 1);
  dup2(connection, 0);
  close(connection);
  execlp("serverprog", "serverprog", 0);
  perror("execlp");
  exit(1);
}
The following code implements a concurrent server for serverprog, running at machine plank and port 5005. It makes sure that no more than maxcli clients are attached to the server at any one time.
/* This is csm.c */

#include < stdio.h >
#include "socketfun.h"

main(int argc, char **argv)
{
  int sock, connection, dummy, nclients, maxcli;

  maxcli = atoi(argv[1]);
  sock = serve_socket("plank", 5005);
  nclients = 0;

  while(1) {
    if (nclients < maxcli) {
      connection = accept_connection(sock);
      nclients++;
      if (fork() == 0) {
        close(sock);
        doserver(connection);
      } else {
        close(connection);
      }
    } else {
      wait(&dummy);
      nclients--;
    }
  }
}
This program has a slight problem with zombie processes. Specifically, at any one time, there may be as many as maxcli-1 zombie processes on the system.

Your job is to write a new version of csm.c that never has zombie processes. I'll sketch how.

Instead of forking off a child that calls doserver(), you will fork off a grandchild that calls doserver() as an orphan. Thus it can never become a zombie. The main csm process has to be able to know when a grandchild is done. It can do this by having a pipe open to each grandchild. Instead of calling wait() as above, it can call select() on all the pipes to find out which ones have been closed because the grandchildren have exited. For each such pipe, it can close the pipe, and decrement nclients.

Write this program.

More hints

Don't panic. Yes, this is some involved code, but take it one step at a time. There are three parts of the above code that you will have to alter: initialization, servicing a client, and determining which clients have finished. Start first by writing code to service a client with a grandchild without worrying about the pipes. That should be simple. Now, figure out how you're going to store the pipes, then write the initialization code. Now do the rest, and when you're done, recopy everything so that I can read it. My solution was about 50 lines of code.

Finally if you're having a hard time making the pieces fit together, make it easy for me to give you partial credit. Don't just write a bunch of junk with pipe()'s and select()'s and fork()'s. Comment what you do, and if you still think it's wrong, explain what you're trying to do.