// GOTO used for two things in kernel (and elsewhere too)

// (a) to flatten a function, simplify code as seed in 8.c.  Here, all GOTOs
// are forward ones, and going towards the end of the fxn.

// (b) to implement a "state machine", where each state is a single GOTO
// label, the code block for each label represents what happens in that
// state, and inside that code block you check for various conditions and
// then "GOTO" the next state.  In this case, you may GOTO backwards or
// forwards as needed.  Each code block also usually has a "break" statement
// and there may be an enclosing loop that iterates over all inputs.

// 1. the need for set_fs/get_fs.

/*
 * Read "len" bytes from "filename" into "buf".
 * "buf" is in kernel space.
 */
int
wrapfs_read_file(const char *filename, void *buf, int len)
{
    struct file *filp;
    mm_segment_t oldfs; // alloc a temp to hold the cur segment "FS" reg status
    int bytes;

    /* Chroot? Maybe NULL isn't right here */
    filp = filp_open(filename, O_RDONLY, 0);
    if (!filp || IS_ERR(filp)) {
	printk("wrapfs_read_file err %d\n", (int) PTR_ERR(filp));
	return -1;  /* or do something else */
    }

    if (!filp->f_op->read) /* better: use vfs_read() */
	return -2;  /* file(system) doesn't allow reads */

    /* now read len bytes from offset 0 */
    filp->f_pos = 0; /* start offset */
    oldfs = get_fs();
    set_fs(KERNEL_DS);
    /* better: use vfs_read() -- MUST CHECK LATEST vfs_read/vfs_write CODE */
    // Note: 'buf' most of the time is a __user ptr
    // but what if 'buf' is a KERNEL mem addr?!
    bytes = filp->f_op->read(filp, buf, len, &filp->f_pos);
    set_fs(oldfs);

    /* close the file */
    filp_close(filp, NULL);

    return bytes;
}

// 2. debug techniques

int sys_foo(...)
{
  // recall: kernel has little protections, b/c its the OS's job to manage
  // hardware, and for efficiency we don't add too many extra checks.

  // no user context: so can't use any user-level debug utils!

  // There have been attempts to add some symbolic debuggers into kernels,
  // e.g., KGDB is a subset of GDB in kernel.  Requires you set up a second
  // system with a serial port/line (can be virtual), and the second system
  // will issue commands over a low-level terminal line to the kernel being
  // debugged.  Complex to setup KGDB for linux, and can only debug certain
  // parts, like higher level kernel components, no low-level drivers,
  // modules hard to debug (b/c KGDB may not have symbols for them).

  // There are projects that allow you to run a linux kernel in "user mode",
  // called User Model Linux (UML).  Then the entire linux kernel is
  // emulated as user process, and you can debug it with plain 'ol GDB.

  // Problems: OS developers are fairly experienced.  They don't make
  // "silly" mistakes like NULL ptr deref, buf overflow, forget to kfree
  // something, etc.  IOW, regular debuggers don't help them catch these
  // basic mistakes.

  // Bugs that OS developers have to deal with are far more complex, and
  // have to do with locking semantics (races, deadlocks), and performance
  // bugs.  Both kinds of bugs are sensitive to TIMING!  A symbolic debugger
  // will NOT help you catch these kinds of bugs, b/c anything that changes
  // the timing of the running code, may hide/mask (or, if you're lucky,
  // expose), the bug.  (This is also known to anyone who debugs highly
  // concurrent systems, including distributed systems.)

  // if asked "how should I debug my kernel code"?
  // A: "printf is your friend"!
  printk("..."); // works just like printf in userland
  // there are also macros for specific debugging, like pr_debug.
  // note also that printk may take a special parameter before the "format
  // string" to tell it what "category" to map this debug statement to.

  // Any printk from kernel, is formatted as a string, and appended to a
  // circular, lock-free buffer in the kernel, which has a limited space.
  // To ensure that printk is as fast as possible, all strings are appended
  // to this buffer WITHOUT LOCKING! This means that concurrent printk's may
  // trash each others' strings.  A circ buffer means that once you fill the
  // buf, you start back in the beginning and overwrite older strings.

  // Next, a kthread wakes up, picks data from this buf and tries to send it
  // to a userland "syslogd" (system logger daemon) process.  Alternatives
  // are that syslogd wakes up like any other user process, and tries to
  // read new data in that buffer (the buffer is mapped readonly to that
  // process).  The user process can then display the message on the
  // /dev/console, append it to a file in /var/log, fwd it to another host,
  // etc.  Syslogd can also perform different actions depending on the
  // "category" of the message: warning, error, informational, etc.  See man
  // pages for syslogd.

  // Problem 1: printk messages do not show up right away on your screen!
  // There's some delay b/t when you printk and when you'll actually see the
  // message.
  // They may show up more quickly in the kernel console, which you can view
  // with tools like "dmesg", but even the kernel console is a relative slow
  // device with limited "display RAM".

  // Problem 2: if you printk TOO many messages, the buffer can overflow,
  // and override older messages, which'll never be displayed.

  // Worse: too many printks change the timing condition of the code you're
  // debugging, and may make it harder to debug races/concurrency bugs!

  // Problem 3: within a single thread, messages generally show up in order,
  // but not if multiple ktreads issue printks.

  // Solution: don't use too many printks!  Use it sparingly, when needed,
  // and when you're done debugging some code, comment out or remove old
  // debug printks.  Note: you won't see too many printks inside the kernel,
  // they're reserved for truly important kernel-level messages.

  /* useful for tracking code reachability */
#if 1
#define XDBG printk(KERN_DEFAULT "XDBG:%s:%s:%d\n",	\
		    __FILE__, __func__, __LINE__)
#else
  // turn off debug macro at compile time, change to "if 0" above.
#define XDBG
#endif

  // You can then do
  XDBG;
  if (IS_ERR(ptr)) {
    err = PTR_ERR(ptr);
    printf("got error %d\n", err);
    XDBG;
    return err;
  }
  XDBG;

  // If printks are too slow, you can also use one of many assertions:

  // conditional assertion: if 'cond' is true, dumps a kernel stack and
  // "BUG" message with line number, on console and "hangs" the
  // kernel/module in question.  Usually reserved for truly exceptional
  // conditions b/c you don't want to panic your kernel.
  BUG_ON(cond);
  BUG_ON(ptr == NULL); // e.g., inside a fxn that expects to get a non-NULL ptr

  // unconditional assertion
  if (some special condition) {
    BUG(); // always panics kernel
  }

  // like BUG* but will not hang/panic kernel.  Useful if you want to get a
  // warning message somewhere, but the issue wasn't so severe that you had
  // to hang the kernel.  The code after WARN_ON will continue to be executed!
  WARN_ON(cond);

  // if you put a WARN_ON in a popular code region that executes many times,
  // its output can overwhelm your console.
  WARN_ON_ONCE(cond); // same as WARN_ON, but dumps the info only once for
		      // the instance of WARN_ON_ONCE.

  // Main advantage of assertions: MUCH faster than printk!  Where printk
  // can take 100s of instructions at least, assertions are compiled at run
  // time, and usually take only a few instructions.  Thus, assertions are
  // more useful if you suspect a race condition.

  // you can also print a stack trace at any time using
  dump_stack();

  // Please don't use C or C++ statements to turn off large code blocks.
  // It's consider cumbersome, ugly, and bad style.

  // bad v1: cumbersome to enable/disable large code blocks.
  //  if (IS_ERR(ptr)) { // check if there was an error
  //    err = PTR_ERR(ptr);
  //    printf("got error %d\n", err); /* print message */
  //    return err;
  //  }

  // bad v2: Old /* C style comments */ DO NOT NEST!  Syntax error!
  /*
  if (IS_ERR(ptr)) { // check if there was an error
    err = PTR_ERR(ptr);
    printf("got error %d\n", err); /* print message */
    return err;
  }
*/

// best way to turn off code blocks more than a couple of lines
#if 0
  if (IS_ERR(ptr)) { // check if there was an error
    err = PTR_ERR(ptr);
    printf("got error %d\n", err); /* print message */
    return err;
  }
#endif

}

// 3. show stack trace and OOPS trace, how to analyze
// - messages from OOPS (and "negative ptrs)
// - stack trace analysis
// - location within function binary

// 4 develop an intuition for bugs
// - what if the whole system freezes?
// - what if the systems appears to get slower and slower?
// - what if variables/pointers seem to have "strange" values?
// - when to reboot?
// - time b/t when bug happens and when its effects are visible.
// - what to do if you get an OOPS
// - stack trace

// 5. steps to develop hw1
// - take it slowly!
