/**
   @file      IOerrors.c
   @author    Mitch Richling@Mee
   @Copyright Copyright 1994 by Mitch Richling.  All rights reserved.
   @brief     Example program for UNIX I/O and EINTR handling@EOL
   @Keywords  UNIX example file I/O EINTR
   @Std       C89

              This C program is intended to illustrate how to handle
              the interrupt error (EINTR) returns possible with many
              I/O functions.  In general an EINTR error is not an
              error to exit over, and the I/O operation should be
              retried.  This code also demonstrates how to handle
              write calls that are only partly successful.  This kind
              of code is not generally required for file I/O, but is
              the norm for network programs.

              If one is determined to take care of EINTR errors
              correctly, then it is best to wrap the I/O functions
              inside of user defined functions that take care of the
              error conditions in order to reduce the code size and
              complexity.  I have chosen to take care of the EINTR
              errors in-line with no added functions because this
              program only calls each I/O function one time!

   @Tested    
              - Solaris 2.8
              - MacOS X.2
              - Linux (RH 7.3)
*/

#include <sys/stat.h>           /* UNIX stat       POSIX */
#include <sys/types.h>          /* UNIX types      POSIX */
#include <sys/uio.h>            /* BSD  I/O        BSD   */
#include <stdio.h>              /* I/O lib         ISOC  */
#include <string.h>             /* Strings         ISOC  */
#include <dirent.h>             /* UNIX dirs       POSIX */
#include <errno.h>              /* error stf       POSIX */
#include <stdlib.h>             /* Standard Lib    ISOC  */
#include <unistd.h>             /* UNIX std stf    POSIX */
#include <fcntl.h>              /* UNIX file ctrl  UNIX  */

int main(int argc, char *argv[]);

int main(int argc, char *argv[]) {
  int FD;
  char *fileName = "foo";       /* The filename we are going to work with. */
  int returnValue;
  const int maxEINTRers = 10;   /* Maximum number interrupt errors to tolerate */
  int numEINTRers;              /* Counter for number of EINTR errors. */
  int numTries;                 /* Counter for number of write */
  const int maxNumTries = 10;   /* maximum number of write attempts to finish. */
  int numToWrite;               /* Number of chars left to write write. */
  char buf[255];                /* Buffer to hold data for I/O operations. */
  char *writeOffset;

  /* First we open the file. */
  numEINTRers = 0;
  do {
    if((FD = open(fileName, O_WRONLY | O_CREAT | O_TRUNC, S_IRWXU | S_IRWXG | S_IRWXO)) < 0) {
      perror("ERROR: File open");
      if(errno != EINTR)
        exit(10);
      numEINTRers++;
    } /* end if */
  } while((FD < 0) && (numEINTRers < maxEINTRers));

  if(FD < 0) {
    errno = EINTR;
    perror("ERROR: Repeated errors");
    exit(11);
  } /* end if */

  /* Next we write something to it.  Most UNIX systems will always
     write the entire request to disk or they will error, but this is
     not required behavior so we check for partial writes.  In general
     this kind of code is only seen in network I/O programs because it
     is common to have partial writes in such code. */
  numEINTRers = 0;
  numTries = 0;
  numToWrite = 6;
  strcpy(buf, "Hello\n");
  writeOffset = buf;
  do {
    if((returnValue = write(FD, writeOffset, numToWrite - 2)) < 0) {
      perror("ERROR: Write error");
      if(errno != EINTR)
        exit(10);
      numEINTRers++;
    } else {
      printf("Wrote %d of %d bytes", returnValue, numToWrite);
      numToWrite = numToWrite - returnValue;
      printf(", have %d bytes to go.\n", numToWrite);
      if(numToWrite > 0)
        writeOffset += returnValue;
      numTries++;
      numEINTRers = 0;          /* Reset so we only exit on maxEINTRers REPEATED errors. */
    } /* end else */
  } while((numToWrite > 0) && (numTries < maxNumTries) && (numEINTRers < maxEINTRers));

  /* Figure out if it worked, and if not print out why. */
  if(numToWrite > 0) {
    printf("Write failure.  Have %d bytes left to write.\n", numToWrite);
    if(numTries >= maxNumTries)
      printf("Exit because we had too many successful, but incomplete, write attempts.\n");
    if(numEINTRers >= maxEINTRers)
      printf("Exit because we had too many repeated EINTR errors.\n");
    exit(12);
  } /* end if */

  /* We must close the file to free up system resources.  We must
     check for errors on the close as well as the other I/O functions.
     If the close fails we may not get the last few writes committed
     to disk.  If we forget to close the file, then it will be closed
     for us when our program exits.  One should not let the OS close
     files because if a problem occurs, the program will never know
     and can thus never take action. */
  numEINTRers = 0;
  do {
    if((returnValue = close(FD)) < 0) {
      perror("ERROR: File close");
      if(errno != EINTR)
        exit(13);
      numEINTRers++;
    } /* end if */
  } while((returnValue < 0) && (numEINTRers < maxEINTRers));

  if(returnValue < 0) {
    errno = EINTR;
    perror("ERROR: Repeated errors");
  } /* end if */

  exit(0);
} /* end func main */

