// allocating resources in the kernel, releasing them, and coding
// conventions.
int sys_encrypt_file(char *infile, char *outfile,
		     void *key, int keylen, int flags)
{
  // 1. validate all the params

  // 2. initialize resources
  // open the infile
  // open the outfile




  // The heart of this syscall:
  // - Main loop: copies one file to the other
  // - read from infile
  // - (enc/decrypt the data)
  // - write to outfile
  // - end loop when (1) error happened, or (2) no more data to process
  // Q: how much data should we process?
  // A1: suppose we read 4 bytes at a time? [too small]
  // A2: why not read whole file at once into one big buffer? [maybe too
  //     large, what if file is 10TB in size!]
  // A3: best unit size to process files or memory, is the "native" unit
  // size of memory or files.  Mem page is 4KB often; disk sector/block is
  // either 512B or 4KB.
  // Always think about the "native" unit of the subsystem in question: for
  // example, if networking, maybe design your protocol so messages can fit
  // into a neat 1500B sizes (MTU -- Max Transmission Unit).  But on 10GB
  // networks, you may have "jumbo packets" that are larger than 1500B.
  // For storage/files, esp. if you want to store things in memory, then a
  // 4KB page size is the most optimal unit.  Actually, it can also be a
  // small integer multiple of the page size: 2 pages, 4 pages, 8 pages,
  // etc.  Usually we go by powers-of-2 b/c a lot of data structures inside
  // kernels are designed to work more efficiently in powers-of-2.
  // Avoid hard-coding the sizes, like 4KB, but rather use functions that
  // get you the page size, like getpagesize(2), or macros in the kernel
  // code, like PAGE_SIZE or PAGE_CACHE_SIZE.

  // end: return status or error
}

// allocating memory, I need a 4KB scratch buffer to operate on
int sys_foo1(...)
{
  char buf[PAGE_SIZE]; // "automatic" var
  int i; // auto var -- it gets "allocated" when you enter, and de-alloc'd
	 // upon exit from function.  Where: on the stack!

  // init buf to all zeros
  // use this 'buf' to read/write data

  // PROBLEM:
  // in userland, you have an illusion of virtual memory (4GB or
  // more of address space), the STACK seg usually starts at the bottom of
  // the addr space and grows "up" towards smaller mem addrs.  Recall
  // there's a large "gap" b/t the HEAP and STACK segments, meaning both
  // segments can grow quite a bit.

  // A kernel does NOT have "unlimited" mem space, uses physical memory.  So
  // as your stack grows, you're taking up more and more physical memory.
  // Another issue is that inside the kernel, we may not have a lot of
  // CONTIGUOUS memory, so if a stack keeps growing, I might have to
  // fragment it, making STACK mgmt more complex.  Thus, most OSs restrict
  // the size of the C stack for running code (kthreads, syscalls, etc.).
  // Usually, the stack size is 8KB in Linux, but can be configured at
  // compile time to a different size.  If you make it too small, won't be
  // able to call enough "depth" of functions; if it's too large, wasting
  // precious kernel memory.  The "kstack" is fixed in size, and preserved
  // until a syscall or kthread is done.

  // If you exceed the kstack size, b/c you put too much stuff on the stack,
  // the kernel may hang, crash, corrupt data, etc.  There's little
  // protection in Linux against exceeding the stack size: any such checking
  // will slow the kernel too much.
  // Why not use virt mem inside the kernel, for the stack?! [Also will be
  // slow, and requires V2P translation for every mem addr on the kstack.]

  // Linux has a Perl script in scripts/checkstack.pl that can analyze a
  // compiled kernel binary and find out the stack size of each function.
  // Often used to determine if some fxns are "too bloated" and optimize
  // them.  The script does NOT tell you what's the deepest possible call
  // path (fxn 1 calling f2, calling f3, etc.).  It's possible to load up
  // and use kernel modules/drivers that will cause a deep call path to
  // exceed the kstack size: then your only choice is to rebuild a kernel w/
  // a larger kstack size.

  // Recursion is disallowed in the kernel!  Considered "very bad."

  // in Sum, placing a buf of 4KB on the stack is bad, b/c you're consuming
  // too much of the limited kstack size!
}

// allocating memory dynamically
int sys_foo2(...)
{
  void *buf;

  // Linux has multiple mem allocators that we'll discuss, but the simplest
  // and most versatile one is similar to malloc().  You can allocate any
  // size you wanted, but kmalloc will be more efficient if it's powers of
  // two, esp. multiple of PAGE sizes.
  buf = kmalloc(PAGE_SIZE, GFP_KERNEL); // flag means: WAIT + IO + FS
  // check if buf is NULL! else use buf
  if (buf == NULL) {
    // maybe log a message
    printk("kmalloc failed in %s at line %d\n", __func__, __LINE__);
    return -ENOMEM; // recall, in linux you return negative numbers
  }

  // kmalloc takes flags, unlike in userland, see <include/linux/gfp.h>
  // You can logically OR the flags to get different functionality.
  // __GFP_WAIT: you're ok to wait and reschedule this thread for "some time".
  // __GFP_ZERO: return page zeroed out (otherwise you have to memset())
  //	NOTE: in userland, when you get a new buf from kmalloc, often it's
  //    zero'd out.  In the kernel, it'll the opposite -- the buffer will
  //    have some old data in it.
  // __GFP_HIGHMEM: allocate in "high mem" region (could get more mem, but
  // may be slower due to CPU cache flushing)
  // __GFP_REPEAT: try very hard to alloc mem, but it may still fail
  // __GFP_NOFAIL: try forever
  // __GFP_IO: can start physical I/O with this mem (e.g., reading from disk)
  // __GFP_NORETRY: don't try to alloc mem too hard
  // __GFP_USER: alloc virt mem for a user process
  // other flags for allocating mem atomically inside dev. drivers, for file
  // systems, and more.

  // release mem: note kfree does NOT zero out the buffer, and does NOT
  // nullify the ptr 'buf'.  It's faster, but YOU have to be careful not to
  // trash memory or use a ptr after it's been freed.  Kernel also does NOT
  // protect any buffer from an under/overflow bug!
  kfree(buf);
  // Important: for every successful kmalloc() there should be exactly one
  // kfree().  If not, you'd be leaking kernel memory.  Kmem leaks are bad
  // b/c they take precious physical mem away from the OS and users, and
  // won't be reclaimed until entire system is rebooted.

}

// opening files in the kernel
int sys_foo3(__user *fname)
{
  struct file *filp; // 'struct file' in OS corresponds to a "file
		     // descriptor' in user level.  A struct task has an
		     // array of "struct file" ptrs, where the first entry
		     // (fd 0) corresponds stdin; fd 1 is for stdout; fd 2
		     // is for stderr; etc.

  // opening a file for reading only
  filp = filp_open(filename, O_RDONLY, 0);
  // check for errors (TBD)

  // Q: Why not call sys_open directly?
  // A: not all functions are exported to other kernel components.  Some
  // functions are exported for static kernel code, but not for loadable
  // modules.  Most importantly: sys_* have usually some special handling
  // (copying registers set by user-level libc code), which would not work
  // if you called it directly; also, sys_* expect __user ptrs, not kernel
  // mem (very bad to mix those).  Also, it's considered "bad form" to call
  // the syscall entry points: OS developers provide specific helper
  // functions and utility functions that are useful for other kernel
  // developers.

  // cleanup
  filp_close(filp, NULL);
}

// Next time:
// 1. different error handling from functions
// 2. initialize multiple steps, multi-step cleanup

