// error handling
int sys_foo(...)
{
  // any time you call ANY function, you should know what the function does.
  // 1. what params it takes
  // 2. which params are "readonly" vs. "writeable"
  // 3. which params represent kernel vs. __user addrs
  // 4. what the function returns
  // - what is the full range of values that can be returned (an int is 2^32)
  // - what values indicate "full success"
  // - what values indicate "partial success"
  // - what values indicate an error, and what is the error code (if any)?
  // 5. what are the pre-conditions before entering the function
  // - e.g., do pointers have to be initialized, contain valid values,
  //   should the system be in a specific state, should certain objects be
  //   un/lock, allocated, etc.?  All this should ideally be documented
  //   somewhere (e.g., above the fxn itself).
  // 6. what are the expected post-conditions after exiting the function,
  //    and which post-conditions need to be true if the fxn returned success
  //    vs. failure.
  // - e.g., you may be expected to free or unlock an object on
  //   success/failure, or maybe the fxn has to return a locked or kmalloc'd
  //   object, or a reference count (TBD) needs to be incremented/decrement,
  // etc.


  // handling return from functions, including errors
  // 1. return 0 on success -errno on failure (never return >0)
  // 2. returns >=0 on success, -errno on failure
  // - e.g., is reading data from a file, similar to read(2)
  // - this is an example of a function that could return a partial success
  // (or partial failure): you asked to read N bytes, you got back fewer
  // than N.
  // 3. return NULL on failure, or PTR on success: kmalloc()
  // - assumption is that if you get NULL back, return -ENOMEM.
  // 4. there are some Boolean functions in the kernel, that return true (1)
  // on success, and false (0) on failure.
  // - be careful not to mix boolean and non-boolean functions, in terms of
  // what's a success or failure.
  // 5. Rare but some functions return "tri-state", three different states
  // can be returned.  Example is ->d_revalidate function from the VFS (TBD),
  // - return -errno on error
  // - return 0 on "success" (cached object found and is valid)
  // - return 1 to indicate that the (cached) object found is invalid and
  //   should be removed from the cache, and re-retrieved from its source.

  // 6. Return an "encoded pointer"
  // - useful for fxn to return both success and failure (and any kind of
  //   error number) in a single return value.
  // - that works ok for functions that return small positive numbers, such
  //   as back from a read(2), not so for function that may return all
  //   possible values back from the function, as a valid value.
  // - consider a function that returns a pointer address.  A pointer, is
  //   just a mem addr.  So in theory, every possible value in 2^32 is a
  //   possibly valid mem addr (exception for '0').  If the only invalid
  //   value is 0, kmalloc can subsume that value and indicate to the caller
  //   that no more memory was found, -ENOMEM.
  // - but how can other functions return ANY possible ptr value as well as
  //   one of several hundreds of possible errors?
  // - Solution: use an "encoded pointer", a way to "reserve" part of the
  //   number space, for errors.

  // - An unsigned 32-bit integer has values from 0 .. (2^32)-1.  So,
  //   reserve the last (say) 1024 values of that range, to indicate an
  //   error.  Meaning that if you get a value of 2^32-N, it means that
  //   error N took place, as long as "N" is <=1024.  Any other number is
  //   considered a valid POINTER addrs: from 0 .. (2^32)-1024.  This lets
  //   you "encode" errors in a small part of the addr space, taking up room
  //   for some valid mem addrs, but allowing the return of both valid ptrs
  //   and errors.

  //   Linux chose higher mem addrs for the errors, b/c often, the high mem
  //   range of addrs is occupied by memory mapped by devices into the
  //   physical addr space of the system (happens at boot time by the
  //   BIOS).  For example, if you are on a 32-bit system, and you have a
  //   video card with 512MB of video ram, that card's memory will show up
  //   to the OS at addrs from 3.5-4G.

  struct file *filp;
  filp = filp_open(filename, O_RDONLY, 0);
  if (IS_ERR(filp)) { // check if 'filp' encodes an error or valid ptr
    // Note: it's wrong to test if "filp == NULL" or "!= NULL"
    err = PTR_ERR(filp); // 'decode' the -errno out of the ptr
    printf("...");
    return err;
  } else {
    // 'filp' is a valid struct file ptr, and you can use it.
  }

  // you can encode an error into a 'ptr' if that's what you're supposed to
  // return, using
  ptr = ERR_PTR(-ENOENT);
  return ptr;

}

// version 1 of our fxn, problems
// 1. duplicate code, repeated over: changes have to happen in multiple
// places (e.g., if you just wanted to change the name of some var).

// 2. code is "fragile" and prone to errors, you have to init/cleanup in a
// precise order; if you ever wanted to change the order of your inits,
// you'd have to restructure the rest of the code too

// 3. Hard to debug such code, b/c there are multiple places where you exit
// the function (e.g., suppose you wanted to add a printf right before you
// exit).
int sys_encrypt_file1(char *infile, char *outfile,
		      void *key, int keylen, int flags)
{
  void *kinfile, *koutfile;
  struct file *in_filp, *out_filp;
  void *buf;
  int ret; // used for return value or error

  // assume params already checked, now time to initialize
  // figure out what order to do these? there are some dependencies: can't
  // open a file until you have the string name for it in a kbuf.
  // Rule: try first the things that are most likely to fail, b/c you'd be
  // able to return more quickly and avoid having to undo too many things
  // you've already successfully done.

  kinfile = getname(infile);
  if (IS_ERR(kinfile)) {
    return PTR_ERR(kinfile); // no need to free, b/c error
  }

  in_filp = open(kinfile, ...);
  if (IS_ERR(in_filp)) {
    putname(kinfile); // MUST: else memleak!
    return PTR_ERR(in_filp);
  }

  koutfile = getname(outfile);
  if (IS_ERR(koutfile)) {
    putname(kinfile); // MUST: else memleak!
    filp_close(in_filp); // close the opened file object
    return PTR_ERR(koutfile);
  }
  out_filp = open(koutfile, ...);
  if (IS_ERR(out_filp)) {
    putname(kinfile); // MUST: else memleak!
    filp_close(in_filp); // close the opened file object
    putname(koutfile); // MUST: else memleak!
    return PTR_ERR(out_filp);
  }

  buf = kmalloc(PAGE_SIZE);
  if (buf == NULL) { // ENOMEM
    putname(kinfile); // MUST: else memleak!
    filp_close(in_filp); // close the opened file object
    putname(koutfile); // MUST: else memleak!
    filp_close(out_filp); // close the opened file object
    return -ENOMEM;
  }

  // here: the inner read+write + enc/decrypt function
  // an error in the middle CAN happen, and you'd need to exit that inner
  // loop and handle that cleanup appropriately, else, you can return
  // success.

  // near the bottom of the function, cleanup again
  putname(kinfile); // MUST: else memleak!
  filp_close(in_filp); // close the opened file object
  putname(koutfile); // MUST: else memleak!
  filp_close(out_filp); // close the opened file object
  kfree(buf);
  return ret; // assume ret was set to 0 (OK) or -errno (error)
}

// v2: avoid duplicate code by nesting if-then-else
// Problems: you may have saved code duplication, but code is still fragile,
// and may even be harder to track (too many levels of nesting).
int sys_encrypt_file2(char *infile, char *outfile,
		      void *key, int keylen, int flags)
{
  init1(); // initialize the first thing
  if (init1_failed) {
    return err;
  } else {
    init2(); // initialize the second thing
    if (init2_failed) {
      // cleanup of only init 1
      return err;
  } else {
      ....
    }
  }
}

// v3: "flatten" the hierarchy using GOTOs
// Benefits: code is "flat", no deep if-then-else nesting
// no code duplication
// easier to restructure, reorder the init and cleanup portions
// easier to add new init/cleanups as needed
// only one exit place (useful for debugging)
// one problem: if you have lots of init/cleanup, there could be a lot of
// goto labels.
int sys_encrypt_file3(char *infile, char *outfile,
		      void *key, int keylen, int flags)
{
  void *kinfile, *koutfile;
  struct file *in_filp, *out_filp;
  void *buf;
  int ret; // used for return value or error

  // assume params already checked, now time to initialize
  // figure out what order to do these? there are some dependencies: can't
  // open a file until you have the string name for it in a kbuf.
  // Rule: try first the things that are most likely to fail, b/c you'd be
  // able to return more quickly and avoid having to undo too many things
  // you've already successfully done.

  kinfile = getname(infile);
  if (IS_ERR(kinfile)) {
    ret = PTR_ERR(kinfile);
    goto out;
  }
  in_filp = open(kinfile, ...);
  if (IS_ERR(in_filp)) {
    ret= PTR_ERR(in_filp);
    goto out_kinfile;
  }
  koutfile = getname(outfile);
  if (IS_ERR(koutfile)) {
    ret = PTR_ERR(koutfile);
    goto out_in_filp_close;
  }
  out_filp = open(koutfile, ...);
  if (IS_ERR(out_filp)) {
    ret =  PTR_ERR(out_filp);
    goto out_koutfile;
  }
  buf = kmalloc(PAGE_SIZE);
  if (buf == NULL) { // ENOMEM
    ret = -ENOMEM;
    goto out_out_filp_close;
  }

  // here: the inner read+write + enc/decrypt function
  // an error in the middle CAN happen, and you'd need to exit that inner
  // loop and handle that cleanup appropriately, else, you can return
  // success.

  // e.g., a failure happens in the middle of the inner loop
  if (write_filed_with_EIO) {
    ret = -EIO;
    goto out_kfree;
  }

  // once you "exit" the inner loop, you come here, and you fall through the
  // rest of these "out" labels, thus doing the cleaning needed.

  // near the bottom of the function, cleanup again with labels
 out_kfree:
  kfree(buf);
 out_out_filp_close:
  filp_close(out_filp); // close the opened file object
 out_koutfile:
  putname(koutfile); // MUST: else memleak!
 out_in_filp_close:
  filp_close(in_filp); // close the opened file object
 out_kinfile:
  putname(kinfile); // MUST: else memleak!
 out:
  // easy to add a debugging printk statement
  return ret; // assume ret was set to 0 (OK) or -errno (error)
}

// same as above, but using only 1 GOTO label
// benefits: only 1 goto label
// slightly slower code, b/c have to init all variables and have to do extra
// "if" checks at the bottom.
// otherwise same benefits of v3 above.
int sys_encrypt_file4(char *infile, char *outfile,
		      void *key, int keylen, int flags)
{
  void *kinfile = NULL, *koutfile = NULL;
  struct file *in_filp = NULL, *out_filp = NULL;
  void *buf = NULL;
  int ret; // used for return value or error (may need to be initialized)

  // assume params already checked, now time to initialize
  // figure out what order to do these? there are some dependencies: can't
  // open a file until you have the string name for it in a kbuf.
  // Rule: try first the things that are most likely to fail, b/c you'd be
  // able to return more quickly and avoid having to undo too many things
  // you've already successfully done.

  kinfile = getname(infile);
  if (IS_ERR(kinfile)) {
    ret = PTR_ERR(kinfile);
    goto out;
  }
  in_filp = open(kinfile, ...);
  if (IS_ERR(in_filp)) {
    ret= PTR_ERR(in_filp);
    goto out;
  }
  koutfile = getname(outfile);
  if (IS_ERR(koutfile)) {
    ret = PTR_ERR(koutfile);
    goto out;
  }
  out_filp = open(koutfile, ...);
  if (IS_ERR(out_filp)) {
    ret =  PTR_ERR(out_filp);
    goto out;
  }
  buf = kmalloc(PAGE_SIZE);
  if (buf == NULL) { // ENOMEM
    ret = -ENOMEM;
    goto out;
  }

  // here: the inner read+write + enc/decrypt function
  // an error in the middle CAN happen, and you'd need to exit that inner
  // loop and handle that cleanup appropriately, else, you can return
  // success.

  // e.g., a failure happens in the middle of the inner loop
  if (write_filed_with_EIO) {
    ret = -EIO;
    goto out;
  }

  // once you "exit" the inner loop, you come here, and you fall through the
  // rest of these "out" labels, thus doing the cleaning needed.

  // near the bottom of the function, cleanup again with labels
 out:
  if (buf != NULL)
    kfree(buf);
  if (!IS_ERR(out_filp))
    filp_close(out_filp);
  if (!IS_ERR(koutfile))
    putname(koutfile);
  if (!IS_ERR(in_filp))
    filp_close(in_filp);
  if (!IS_ERR(kinfile))
    putname(kinfile);
  return ret;
}

// Next time:
// 1. the need for set_fs/get_fs.
// 2. debug techniques
// 3. steps to develop hw1
