// TODO: buffered I/O, fopen, and discrepancies b/t buf and size

/* copy a file */
main()
{
  int fd1;
  char buf[100];
  int len = 100;

  // 1. open file 1 for reading

  fd1 = open(file1, args);
  if (fd1 < 0) { // failed
# if 0
    printf("open failed with error code %d\n", errno);
    printf("open failed: %s\n", strerror(errno));
#endif
    perror(file1); // print arg given then ": " then strerror of errno
    exit(1); // tells caller (e.g., a shell script) that we failed
  }
  // 2. open file 2 for writing
  // 3. read from file 1
  rv = read(fd1, buf, len);
  if (rv < 0) { // failed
    perror("read");
    exit(1);
  }
  if (rv == len) { // complete success
  }
  if (rv == 0) { // reached EOF
  }
  // else we've got a partial read (next time: files vs. sockets)

  // 4. write to file 2
  // loop steps 3+4 until done

  // don't forget to close file descriptors.  Bad esp. for long running
  // programs! OS will refuse to give you more than a set no. of open files,
  // and then you'll get errors of even OS will kill your prog.

  // 5. close file 2
  close(something);// remember to close fd2
  // 6. close file 1

}

// illustrate multi stage error handling: v1
// code is fragile and prone to errors esp. if you reorder the init stages,
// or add new init stages in the middle.  Also code is duplicated.  Also,
// you have multiple exit points in program (harder for debugging).
main2()
{
  int bufsize = getpagesize(); // probably 4K
  void *buf;
  char *infile; // get it from args passed to main
  char *outfilefile; // get it from args passed to main
  int fd1, fd2;

  // 1. alloc a buffer
  buf = malloc(bufsize);
  if (buf == NULL) {
    perror("malloc");
    exit(1);
  }

  // 2. open infile
  fd1 = open(infile, O_RDONLY);
  if (fd1 < 0) {
    perror(infile);
    free(buf); // cleanup
    exit(1);
  }

  // 3. open outfile
  fd2 = open(outfile, O_WRONLY);
  if (fd2 < 0) {
    perror(outfile);
    free(buf); // cleanup
    close(fd1); // cleanup
    exit(1);
  }

  // here is the main code: read/write loop

  // close fd2
  close(fd2);
  // close fd1
  close(fd1);
  // free buf
  free(buf);
  // exit succesfully
  exit(0); // assuming that if we reach here, all's well (is it?)
}

// illustrate multi stage error handling: v2
// bad: too much indentation, very hard to track code
main3()
{
  int bufsize = getpagesize(); // probably 4K
  void *buf;
  char *infile; // get it from args passed to main
  char *outfilefile; // get it from args passed to main
  int fd1, fd2;

  // 1. alloc a buffer
  buf = malloc(bufsize);
  if (buf == NULL) {
    perror("malloc");
    exit(1);
  } else {
    // 2. open infile
    fd1 = open(infile, O_RDONLY);
    if (fd1 < 0) {
      perror(infile);
      free(buf); // cleanup
      exit(1);
    } else {
      // 3. open outfile
      fd2 = open(outfile, O_WRONLY);
      if (fd2 < 0) {
	perror(outfile);
	free(buf); // cleanup
	close(fd1); // cleanup
	exit(1);
      }
    }
  }


  // here is the main code: read/write loop

  // close fd2
  close(fd2);
  // close fd1
  close(fd1);
  // free buf
  free(buf);
}

// illustrate multi stage error handling: v3
// w/ careful use of GOTO's, code is not deeply indented, no code
// duplication, more symmetric initialization code, easier to
// maintain/debug, single exit.
main3()
{
  int bufsize = getpagesize(); // probably 4K
  void *buf;
  char *infile; // get it from args passed to main
  char *outfilefile; // get it from args passed to main
  int fd1, fd2, retval;

  // 1. alloc a buffer
  buf = malloc(bufsize);
  if (buf == NULL) {
    perror("malloc");
    retval = 1; // indicates to caller that malloc failed
    goto out:
  }

  // 2. open infile
  fd1 = open(infile, O_RDONLY);
  if (fd1 < 0) {
    perror(infile);
    retval = 2; // indicates to caller that open infile failed
    goto out_free:
  }

  // 3. open outfile
  fd2 = open(outfile, O_WRONLY);
  if (fd2 < 0) {
    perror(outfile);
    retval = 3; // indicates to caller that open outfile failed
    goto out_close1:
  }

  // here is the main code: read/write loop.  if inside r/w loop, something
  // bad happens (any serious error), you can do cleanup (e.g., remove temp
  // files, rename, etc.), set retval (to a non-zero), and goto out_close2.
  // Also, if the inner loop completely succeeded, set retval=0, and then
  // goto out_close2 (or exit the inner-most r/w loop and "fall through" to
  // the out_close2 label).

 out_close2: // your code will fall-through these goto lables
  // close fd2
  close(fd2);
 out_close1:
  // close fd1
  close(fd1);
 out_free:
  // free buf
  free(buf);
 out:
  exit(retval);
}

// illustrate multi stage error handling: v4
// variant of v3 that uses only one goto label, can be simpler esp. if your
// code would have needed too many goto labels.  Minor disadvantages: you
// will need to cleanp only if cleanup is needed (a bit more CPU cycles and
// instructions); and you also have to initialize all variables that are
// checked at the end cleanup code (again, a bit more effort at runtime).
main4()
{
  int bufsize = getpagesize(); // probably 4K
  void *buf = NULL;
  char *infile; // get it from args passed to main
  char *outfilefile; // get it from args passed to main
  int fd1 = -1, fd2 = -1, retval = 0;

  // 1. alloc a buffer
  buf = malloc(bufsize);
  if (buf == NULL) {
    perror("malloc");
    retval = 1; // indicates to caller that malloc failed
    goto out:
  }

  // 2. open infile
  fd1 = open(infile, O_RDONLY);
  if (fd1 < 0) {
    perror(infile);
    retval = 2; // indicates to caller that open infile failed
    goto out:
  }

  // 3. open outfile
  fd2 = open(outfile, O_WRONLY);
  if (fd2 < 0) {
    perror(outfile);
    retval = 3; // indicates to caller that open outfile failed
    goto out:
  }

  // here is the main code: read/write loop
  // if inside r/w loop, something bad happens, you can do cleanup, set
  // retval, and goto out_close2. Also, if the inner loop completely
  // succeeded, set retval=0, and then goto out_close2.

 out:
  // close fd2
  if (fd2 >= 0)
    close(fd2);
  // close fd1
  if (fd1 >= 0)
    close(fd1);
  // free buf
  if (buf != NULL)
    free(buf);
  exit(retval);
}

// option processing using getopt(3)
// must pass argc: denotes number of args on cmd line
// argv is an array of size argc, where each arg is a char* (null terminated
// string).  argv is a double-pointer to a "char".
// special: argv[0], the name of the program or binary running
main(int argc, char *argv[])
{
  int opt_e = 0, opt_d = 0, opt_h = 0;
  // int opt_D = 0; // superfluous
  u_int opt_D = 0; // all debug flags, if any
  char *opt_p_arg = NULL; // file containing password
  char *infile, *outfile;

  // 3rd arg to getopt(3) is a string here each char is a single-letter
  // option (case sensitive!); a ":" after an option char means that this
  // flag takes a single argument.
  // getopt(3) will handle multiple cases
  //   ./myprog -D arg -ev
  //   ./myprog -e -D arg -v
  //   ./myprog -P arg2 -ve -D arg
  //   ./myprog -d -d infile outfile
  // general rule: cmd first, then all options regardless if they take
  // args, and finally listing args that don't take an option (in/out files)

  // stage 1: recommends: finish processing ALL flags, and then check for
  // validity of flags, and don't start doing actual work.
  while ((opt = getopt(argc, argv, "hvedp:D:")) != -1) {
    switch (opt) {
    case 'd':
      opt_d++;
      break;
    case 'e':
      opt_e++;
      break;
    case 'v':
      opt_v++;
      break;
    case 'D':
      opt_D = XXX; // translate optarg (char *) into int value for opt_D
      break;
    case 'p':
      opt_p_arg = optarg; // translate optarg (char *) into int value for opt_D
      break;
    case 'h':
      print_usage(); // print usage string
      exit(1);
      break;
    default:
      print_usage(); // print usage string, combine with 'h' opt above
      exit(EXIT_FAILURE);
    }
  }
  infile = argv[optind];
  outfile = argv[optind + 1];

  // stage 2: validate semantic use of flags
  if (opt_d > 1) { // strict, not allowing to specify decrypt flag more than
		   // once
    print_usage(); // ensure that print_usage TELLS users that they can only
		   // specify -d or -e ONCE.
    exit(1);
  }
  if (opt_d == 0 && opt_e == 0) // bad, must specify -d or -e
    print_usage_and_exit();
  if (opt_d != 0 && opt_e != 0) // bad, can't specify both -d and -e
    print_usage_and_exit();
  // ensure that infile+outfile are NOT full
  // ensure that user didn't pass a 3rd arg after outfile
  // other validations...

  // stage 3: preparatory work
  // if infile or outfile are a '-' -- handle specially
  // suggest you create two descriptors -- fd_in, and fd_out, and set them
  // based on '-' or actual filename you have to open.

}
