Download | Plain Text | Line Numbers


/*
 * Name:         calculator
 * Author:       Manuel Mausz, 0728348
 * Description:  upon start calculator forks a child. the parent reads messages
 *               from stdin, forwards the messages to the child, which does the
 *               calculation. the result is forwarded to the parent and dumped
 *               to stdout.
 * Created:      24.05.2009
 * Exercise:     3c
 */
 
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "error.h"
 
/* maximal input length */
#define MAX_LEN 15
 
/* install signal handler macro */
#define INSTALL_SIGNAL(signum, act, oldact) \
  sigaction(signum, NULL, &oldact); \
  if (oldact.sa_handler != SIG_IGN) \
    sigaction(signum, &act, NULL);
 
/* global variables */
static int pipew[2];          /* fds for write to child/read from parent */
static int piper[2];          /* fds for read from child/write to parent */
static FILE *streamw = NULL;  /* stream for writing to child */
static FILE *streamr = NULL;  /* stream for reading from child */
 
/*
 * NAME: sigpipe_handler
 * PURPOSE:
 * signal handler for SIGPIPE
 * sets global error flag to 1
 *
 * PARAMETERS:
 * int signum ... signal number
 *
 * RETURN VALUE:
 * void
 *
 * GLOBAL VARS:
 * error, child
 */
static void sigpipe_handler(int signum)
{
#ifdef DEBUG
  (void) printf("%s SIGPIPE received\n", (child) ? "[CHILD]  " : "[PARENT] ");
#endif
  error = 1;
}
 
/*
 * NAME: usage
 * PURPOSE:
 * prints program usage to stderr and terminates with EXIT_FAILURE
 *
 * PARAMETERS:
 * void
 *
 * RETURN VALUE:
 * void
 *
 * GLOBAL VARS:
 * me
 */
static void usage()
{
  (void) fprintf(stderr, "Usage: %s\n", me);
  (void) fprintf(stderr, "$> <number> <number> <operator>\n");
  (void) fprintf(stderr, "BNF: <number>   ::= -?[0-9]+\n");
  (void) fprintf(stderr, "     <operator> ::= +|-|*|/\n");
  (void) bailout(0, NULL);
}
 
/*
 * NAME: allocate_resources
 * PURPOSE:
 * allocate resources
 *
 * PARAMETERS:
 * void
 *
 * RETURN VALUE:
 * void
 *
 * GLOBAL VARS:
 * pipew, piper
 */
void allocate_resources(void)
{
#ifdef DEBUG
  (void) printf("[GLOBAL]  allocating resources...\n");
#endif
  if (pipe(pipew) == -1)
    (void) bailout(1, "Unable to create write pipe!");
  if (pipe(piper) == -1)
    (void) bailout(1, "Unable to create read pipe!");
}
 
/*
 * NAME: free_resources
 * PURPOSE:
 * frees allocated resources
 *
 * PARAMETERS:
 * void
 *
 * RETURN VALUE:
 * void
 *
 * GLOBAL VARS:
 * error, child, streamw, streamr
 */
void free_resources(void)
{
#ifdef DEBUG
  (void) printf("%s freeing resources...\n", (child) ? "[CHILD]  " : "[PARENT] ");
#endif
  /* close write stream */
  if (streamw != NULL)
  {
    if (fclose(streamw) == EOF && !error)
    {
      streamw = NULL;
      (void) bailout(1, "Unable to close write stream!");
    }
    streamw = NULL;
  }
 
  /* close read stream */
  if (streamr != NULL)
  {
    if (fclose(streamr) == EOF && !error)
    {
      streamr = NULL;
      (void) bailout(1, "Unable to close read stream!");
    }
    streamr = NULL;
  }
}
 
/*
 * NAME: exec_parent
 * PURPOSE:
 * code block for parent process
 * prepares pipes and streams, forwards messages read from stdin to child,
 * and dumps responses from child to stdout
 *
 * PARAMETERS:
 * void
 *
 * RETURN VALUE:
 * void
 *
 * GLOBAL VARS:
 * error, pipew, piper, streamw, streamr
 */
void exec_parent(void)
{
  char buffer[MAX_LEN + 1];
 
  /* write pipe: close unused read end */
  if (close(pipew[0]) == -1)
    (void) bailout(1, "Unable to close read end of write pipe!");
  /* read pipe: close unused write end */
  if (close(piper[1]) == -1)
    (void) bailout(1, "Unable to close write end of read pipe!");
 
  /* create write + read stream */
  if ((streamw = fdopen(pipew[1], "w")) == NULL)
    (void) bailout(1, "Unable to open write pipe for writing!");
  if ((streamr = fdopen(piper[0], "r")) == NULL)
    (void) bailout(1, "Unable to open read pipe for reading!");
 
  /* read data from stdin */
  while(!error && fgets(buffer, MAX_LEN, stdin) != NULL)
  {
    /* write data to child */
    if (!error && fprintf(streamw, buffer) < 0)
      (void) bailout(1, "Unable to write to pipe!");
    if (!error && fflush(streamw) == EOF)
      (void) bailout(1, "Unable to flush write stream!");
 
    /* read result from child */
    if (!error && fgets(buffer, MAX_LEN, streamr) != NULL)
    {
      if (printf("Result: %s", buffer) < 0)
        (void) bailout(1, "Unable to write to stdout!");
      if (fflush(stdout) == EOF)
        (void) bailout(1, "Unable to flush stdout!");
    }
    if (!error && ferror(streamr))
      (void) bailout(1, "Unable to read from pipe!");
  }
 
  /* close write stream so the child can terminate */
  if (fclose(streamw) == EOF && !error)
  {
    streamw = NULL;
    (void) bailout(1, "Unable to close write stream!");
  }
  streamw = NULL;
}
 
/*
 * NAME: exec_parent
 * PURPOSE:
 * code block for child process
 * prepares pipes and streams, reads messages from parent, parses the messages
 * and forwards the calculated result to the parent back
 *
 * PARAMETERS:
 * void
 *
 * RETURN VALUE:
 * void
 *
 * GLOBAL VARS:
 * error, pipew, piper, streamw, streamr
 */
void exec_child(void)
{
  char buffer[MAX_LEN + 1];
  int num1, num2, result = -1;
  char op;
 
  /* write pipe: close unused write end */
  if (close(pipew[1]) == -1)
    (void) bailout(1, "Unable to close write end of write pipe!");
  /* read pipe: close unused read end */
  if (close(piper[0]) == -1)
    (void) bailout(1, "Unable to close read end of read pipe!");
 
  /* create write + read stream (note: swapped the two pipes) */
  if ((streamr = fdopen(pipew[0], "r")) == NULL)
    (void) bailout(1, "Unable to open write pipe for writing!");
  if ((streamw = fdopen(piper[1], "w")) == NULL)
    (void) bailout(1, "Unable to open read pipe for reading!");
 
  /* read data from parent */
  while(fgets(buffer, MAX_LEN, streamr) != NULL)
  {
    /* parse line */
    if (sscanf(buffer, "%d %d %c\n", &num1, &num2, &op) != 3)
      (void) bailout(0, "Syntax error!");
 
    /* calculate */
    switch(op)
    {
      case '+':
        result = num1 + num2;
        break;
      case '-':
        result = num1 - num2;
        break;
      case '*':
        result = num1 * num2;
        break;
      case '/':
        result = (num2 == 0) ? 0 : num1 / num2;
        break;
      default:
        (void) bailout(0, "Unsupported operation!");
        break;
    }
 
    /* write result to parent */
    if (fprintf(streamw, "%d\n", result) < 0)
      (void) bailout(1, "Unable to write to pipe!");
    if (fflush(streamw) == EOF)
      (void) bailout(1, "Unable to flush write stream!");
  }
  if (ferror(streamr))
    (void) bailout(1, "Unable to read from pipe!");
}
 
/*
 * NAME: main
 * PURPOSE:
 * install signal handler, allocate resources, fork process,
 * call the corresponding methods and wait for child before terminating
 *
 * PARAMETERS:
 * standard parameters of main
 *
 * RETURN VALUE:
 * int ... 0 on success, 1 on error
 *
 * GLOBAL VARS:
 * me, error, child
 */
int main(int argc, char *argv[])
{
  struct sigaction new_action, old_action;
  pid_t pid, wpid;
  int status;
 
  /* save me */
  me = argv[0];
 
  /* install signal handler */
  new_action.sa_handler = sigpipe_handler;
  sigemptyset(&new_action.sa_mask);
  new_action.sa_flags = 0; /* to avoid restart of blocking syscalls */
  INSTALL_SIGNAL(SIGPIPE, new_action, old_action)
 
  /* check cmdline options */
  if (argc > 1)
    (void) usage();
 
  /* allocate resources */
  (void) allocate_resources();
 
  switch(pid = fork())
  {
    case -1:
      (void) bailout(1, "Unable to fork!");
      break;
    case 0: /* child */
#if DEBUG
      (void) printf("[CHILD]   pid=%d\n", getpid());
#endif
      child = 1;
      (void) exec_child();
      break;
    default: /* parent */
#if DEBUG
      (void) printf("[PARENT]  pid=%d\n", getpid());
#endif
      (void) exec_parent();
 
#if DEBUG
  (void) printf("[PARENT]  waiting for child...\n");
#endif
      do
      {
        wpid = waitpid(pid, &status, 0);
        if (wpid == -1)
          (void) bailout(1, "Error while waiting for child!");
        else if (WIFEXITED(status))
          error += WEXITSTATUS(status);
        else if (WIFSIGNALED(status))
          error += WTERMSIG(status);
      }
      while(!WIFEXITED(status) && !WIFSIGNALED(status));
  }
 
  /* free resources */
  (void) free_resources();
 
  return (error) ? 1 : 0;
}
 
/* vim: set et sw=2 ts=2: */