Plan9 style waserror() handling
authorBarret Rhoden <brho@cs.berkeley.edu>
Thu, 16 Jan 2014 20:49:01 +0000 (12:49 -0800)
committerBarret Rhoden <brho@cs.berkeley.edu>
Thu, 16 Jan 2014 21:00:39 +0000 (13:00 -0800)
Adapted from the latest version we built for the old 9ns branch.

You'll need to #include err.h to use this.  Since it includes some other
files of its own, I didn't want to put it in common.h.

Documentation/plan9.txt [new file with mode: 0644]
kern/include/err.h [new file with mode: 0644]
kern/src/Kbuild
kern/src/err.c [new file with mode: 0644]

diff --git a/Documentation/plan9.txt b/Documentation/plan9.txt
new file mode 100644 (file)
index 0000000..fc957ee
--- /dev/null
@@ -0,0 +1,143 @@
+We're adding the plan 9 file system to akaros.We're bringing in the
+name space interface, including the ability to do binds, union mounts,
+and the like. We will extend it to support things we might need,
+in particular mmap.  We will use it to add things to Akaros we 
+need, such as virtio drivers, ethernet drivers, and TCP.
+
+By bringing this model in, we can make the Akaros interface more powerful,
+yet simpler. We can remove a number of system calls right away 
+and yet still have the functions they provide, for example. 
+
+This is not a from scratch effort but a code translation. The Plan 9 code
+deals with very subtle situations and has been hardened over time. No need
+to relearn what they learned from scratch.
+
+Currently we have a lot of the code in and are testing a first device -- 
+the regression device from NxM.
+
+Issues.
+
+The biggest issue so far is implementing the Plan 9 error handling.
+In Plan 9, errors are managed via a longjmp-like mechanism. For example,
+in a call chain such as:
+a()
+ b()
+  c()
+   d()
+
+It is possible for 'd' to invoke an error that returns to 'a' directly. 
+This model has many advantages over the standard model, which looks like
+this:
+a{ b();}
+ b{ if c() return OK; else return -1;}
+  c{ if d() return OK; else return -1;}
+   d{ if something return OK; else return -1;}
+
+In Plan 9 it can look like this:
+
+a{ b();}
+ b{ c(); something else();}
+  c{ d(); other thing();}
+   d{ if something return OK; else error("bad");}
+
+Note that the intermediate functions can be written as though nothing
+went wrong, since anything that goes wrong gets bounced to the first level
+error handler. 
+
+The handling is implemented via something called waserror().
+In a() we do this:
+
+a(){
+   if (waserror())  { handle error; poperror();}
+   b();
+   poperror();
+}
+
+and in d we might have this:
+d(){
+   do something; 
+   if (bad) error("bad");
+   return 0;
+}
+
+What if there's more than one error? There can be multiple invocations
+of waserror in one function:
+a(){
+   if (waserror()){ printf("b failed"); poperror(); return -2;}
+   b();
+   if (waserror()) { printf("z failed"); nexterror(); }
+   z();
+   poperror();
+   poperror();
+}
+
+Every waserror needs a matching poperror or nexterror in the same function as
+the waserror, covering every exit from the function.  nexterror is like
+to poperror() then error("str"), but without resetting errstr.
+
+Note that the error could have been anywhere in the call chain;
+we don't care. From the point of view of a(), something failed, and we only
+know about b() or z(), so we blame them. We also show in this example
+nexterror(). Nexterror() pops back to the next error in the error stack,
+which might be in this function or somewhere up the call chain.
+
+How do we find out the ultimate blame? Recall that error takes a string,
+and that can be anything. We can tell from that. 
+
+Where does the string in error() go?
+In Plan 9, it goes into the proc struct; in Akaros,
+it's copied to a user-mode buffer via set_errstr(). 
+
+waserror()/error()/nexterror() manipulate an error call stack, similar to
+the function call stack. In Plan 9, this stack is maintained in the proc
+struct. This is cheap in Plan 9, since the compiler has caller-save, and
+hence each stack entry is a stack and a PC. In a callee-save world, the
+stack entries are much, much larger; so large that maintaining the stack
+in the proc struct is impractical.
+
+Hence, we've had to make changes that add a bit of inconvenience but
+leave the code mostly intact. The error code in Akaros is tested in every
+circumstance at this point due to all the bugs we had in our port
+of the Plan 9 file system code.
+
+So, we'll go from the easiest case to the hardest.
+
+Case 1: You're a leaf function that does not use error(). No change.
+
+Case 2: You're a leaf function that needs error().  Just call error().
+
+Case 3: You're an intermediate function that calls functions that use error(),
+even though you do not.  No change.
+
+Those are in some sense the easier cases. Now it starts to get a
+bit harder.
+
+Case 4: you're a leaf or intermediate function that uses waserror().  You need
+to use a macro which creates an array of errbuf structs as automatics. The
+waserror() usage does not change.  The macro is ERRSTACK(x), where x is the
+number of calls to waserror() in your function. See kern/sys/chan.c.  Every call
+to waserror needs to have a matching poperror.  You cannot call nexterror or
+poperror unless you are in the same scope as an ERRSTACK that had a waserror
+call.
+
+Case 5: you're a root node, i.e. you're the start of a chain of calls
+via syscall that must do the "root" errbuf setup, so that all error
+calls eventually return to you. In this case, you need to start the error
+stack.  This uses the same macro as case 4 (ERRSTACK(x)), for now.
+
+Finally, if, in a waserror, you are finished and want to pop out to the
+next error in the chain, either in the same function or up the call stack,
+just call nexterror().
+
+This can be handy for debugging: in any function that supports error(), i.e.
+called from a function that called waserror(), simply call error and it bails
+you out of wherever you are, doing appropriate cleanup on the way out.  No need
+to add return value processing to intermediate functions; if you try this out
+you will likely find it is extremely handy. I really miss it on Linux. It made a
+lot of the port debugging to Akaros a lot easier.
+
+There is error checking in error()/waserror()/nexterror().
+If you overflow or underflow the error stack the kernel will panic.
+The implementation is in kern/src/error.c, the macros are in
+kern/include/plan9.h.
+
diff --git a/kern/include/err.h b/kern/include/err.h
new file mode 100644 (file)
index 0000000..c710032
--- /dev/null
@@ -0,0 +1,32 @@
+/* Plan9 style error popping.  For details, read Documentation/plan9.txt */
+
+#ifndef ROS_KERN_ERR_H
+#define ROS_KERN_ERR_H
+
+#include <setjmp.h>
+#include <syscall.h>
+
+struct errbuf {
+       struct jmpbuf jmpbuf;
+};
+
+/* add 1 in case they forget they need an entry for the passed-in errbuf */
+#define ERRSTACK(x) struct errbuf *prev_errbuf; struct errbuf errstack[(x)];   \
+                    int curindex = 0;
+#define waserror() (errpush(errstack, ARRAY_SIZE(errstack), &curindex,         \
+                            &prev_errbuf) ||                                   \
+                    setjmp(&(get_cur_errbuf()->jmpbuf)))
+#define error(x,...) {set_errstr(x, ##__VA_ARGS__);                            \
+                      longjmp(&get_cur_errbuf()->jmpbuf, 1);}
+#define nexterror() {errpop(errstack, ARRAY_SIZE(errstack), &curindex,         \
+                            prev_errbuf);                                      \
+                     longjmp(&(get_cur_errbuf())->jmpbuf, 1);}
+#define poperror() {errpop(errstack, ARRAY_SIZE(errstack), &curindex,          \
+                           prev_errbuf);}
+
+int errpush(struct errbuf *errstack, int stacksize, int *curindex,
+            struct errbuf **prev_errbuf);
+void errpop(struct errbuf *errstack, int stacksize, int *curindex,
+            struct errbuf *prev_errbuf);
+
+#endif /* ROS_KERN_ERR_H */
index e784dc4..ebdc70c 100644 (file)
@@ -8,6 +8,7 @@ obj-y                                           += console.o
 obj-y                                          += devfs.o
 obj-y                                          += elf.o
 obj-y                                          += env.o
+obj-y                                          += err.o
 obj-y                                          += event.o
 obj-y                                          += ext2fs.o
 obj-y                                          += frontend.o
diff --git a/kern/src/err.c b/kern/src/err.c
new file mode 100644 (file)
index 0000000..1d9aaca
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2013 Google Inc.
+ */
+//#define DEBUG
+#include <setjmp.h>
+#include <vfs.h>
+#include <kfs.h>
+#include <slab.h>
+#include <kmalloc.h>
+#include <kref.h>
+#include <string.h>
+#include <stdio.h>
+#include <assert.h>
+#include <error.h>
+#include <cpio.h>
+#include <pmap.h>
+#include <smp.h>
+#include <err.h>
+
+/* General idea: if we're at the base for this func (base of ERRSTACK in the
+ * scope where ERRSTACK and waserror are used), we need to find and save the
+ * previous errbuf, so we know how to pop back.
+ *
+ * The main goal of this is to track and advertise (via pcpui) the errbuf that
+ * should be jumped to next (when we call error()).  Note that the caller of
+ * this (waserror()) will fill the jumpbuf shortly with its context.
+ *
+ * When we enter, curindex points to the slot we should use.  First time, it is
+ * 0, and we'll set cur_eb to slot 0.  When we leave, curindex is set to the
+ * next free slot. */
+int errpush(struct errbuf *errstack, int stacksize, int *curindex,
+            struct errbuf **prev_errbuf)
+{
+       printd("pushe %p %d %dp\n", errstack, stacksize, *curindex);
+       if (*curindex == 0)
+               *prev_errbuf = get_cur_errbuf();
+
+       if (*curindex >= stacksize)
+               panic("Error stack overflow");
+       set_cur_errbuf(&errstack[*curindex]);
+       *curindex = *curindex + 1;
+       return 0;
+}
+
+/* Undo the work of errpush, and advertise the new errbuf used by error() calls.
+ * We only need to be tricky when we reached the beginning of the stack and need
+ * to check the prev_errbuf from a previous ERRSTACK/function.
+ *
+ * When we enter, curindex is the slot of the next *free* errstack (the one we'd
+ * push into if we were pushing.  When we leave, it will be decreased by one,
+ * and will still point to the next free errstack (the one we are popping).
+ * */
+void errpop(struct errbuf *errstack, int stacksize, int *curindex,
+            struct errbuf *prev_errbuf)
+{
+       printd("pope %p %d %d\n", errstack, stacksize, *curindex);
+       /* curindex now points to the slot we are popping*/
+       *curindex = *curindex - 1;
+       /* We still need to advertise the previous slot, which is one back from
+        * curindex.  If curindex is 0, that means the next free slot is the first
+        * of our errstack.  In this case, we need to advertise the prev. */
+       if (*curindex < 0)
+               panic("Error stack underflow");
+
+       if (*curindex == 0)
+               set_cur_errbuf(prev_errbuf);
+       else
+               set_cur_errbuf(&errstack[*curindex - 1]);       /* use the prior slot */
+}