Merge "FunctionFS: initial implementation"
diff --git a/adb/adb.c b/adb/adb.c
index 37d94a93..6ec4f7a 100644
--- a/adb/adb.c
+++ b/adb/adb.c
@@ -21,6 +21,7 @@
 #include <ctype.h>
 #include <stdarg.h>
 #include <errno.h>
+#include <stddef.h>
 #include <string.h>
 #include <time.h>
 #include <sys/time.h>
@@ -28,6 +29,8 @@
 #include "sysdeps.h"
 #include "adb.h"
 
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
+
 #if !ADB_HOST
 #include <private/android_filesystem_config.h>
 #include <linux/capability.h>
@@ -42,7 +45,9 @@
 
 int HOST = 0;
 
+#if !ADB_HOST
 static const char *adb_device_banner = "device";
+#endif
 
 void fatal(const char *fmt, ...)
 {
@@ -269,6 +274,36 @@
     send_packet(p, t);
 }
 
+static size_t fill_connect_data(char *buf, size_t bufsize)
+{
+#if ADB_HOST
+    return snprintf(buf, bufsize, "host::") + 1;
+#else
+    static const char *cnxn_props[] = {
+        "ro.product.name",
+        "ro.product.model",
+        "ro.product.device",
+    };
+    static const int num_cnxn_props = ARRAY_SIZE(cnxn_props);
+    int i;
+    size_t remaining = bufsize;
+    size_t len;
+
+    len = snprintf(buf, remaining, "%s::", adb_device_banner);
+    remaining -= len;
+    buf += len;
+    for (i = 0; i < num_cnxn_props; i++) {
+        char value[PROPERTY_VALUE_MAX];
+        property_get(cnxn_props[i], value, "");
+        len = snprintf(buf, remaining, "%s=%s;", cnxn_props[i], value);
+        remaining -= len;
+        buf += len;
+    }
+
+    return bufsize - remaining + 1;
+#endif
+}
+
 static void send_connect(atransport *t)
 {
     D("Calling send_connect \n");
@@ -276,9 +311,8 @@
     cp->msg.command = A_CNXN;
     cp->msg.arg0 = A_VERSION;
     cp->msg.arg1 = MAX_PAYLOAD;
-    snprintf((char*) cp->data, sizeof cp->data, "%s::",
-            HOST ? "host" : adb_device_banner);
-    cp->msg.data_length = strlen((char*) cp->data) + 1;
+    cp->msg.data_length = fill_connect_data((char *)cp->data,
+                                            sizeof(cp->data));
     send_packet(cp, t);
 #if ADB_HOST
         /* XXX why sleep here? */
@@ -305,29 +339,56 @@
     }
 }
 
+/* qual_overwrite is used to overwrite a qualifier string.  dst is a
+ * pointer to a char pointer.  It is assumed that if *dst is non-NULL, it
+ * was malloc'ed and needs to freed.  *dst will be set to a dup of src.
+ */
+static void qual_overwrite(char **dst, const char *src)
+{
+    if (!dst)
+        return;
+
+    free(*dst);
+    *dst = NULL;
+
+    if (!src || !*src)
+        return;
+
+    *dst = strdup(src);
+}
+
 void parse_banner(char *banner, atransport *t)
 {
-    char *type, *product, *end;
+    static const char *prop_seps = ";";
+    static const char key_val_sep = '=';
+    char *cp;
+    char *type;
 
     D("parse_banner: %s\n", banner);
     type = banner;
-    product = strchr(type, ':');
-    if(product) {
-        *product++ = 0;
-    } else {
-        product = "";
-    }
-
-        /* remove trailing ':' */
-    end = strchr(product, ':');
-    if(end) *end = 0;
-
-        /* save product name in device structure */
-    if (t->product == NULL) {
-        t->product = strdup(product);
-    } else if (strcmp(product, t->product) != 0) {
-        free(t->product);
-        t->product = strdup(product);
+    cp = strchr(type, ':');
+    if (cp) {
+        *cp++ = 0;
+        /* Nothing is done with second field. */
+        cp = strchr(cp, ':');
+        if (cp) {
+            char *save;
+            char *key;
+            key = adb_strtok_r(cp + 1, prop_seps, &save);
+            while (key) {
+                cp = strchr(key, key_val_sep);
+                if (cp) {
+                    *cp++ = '\0';
+                    if (!strcmp(key, "ro.product.name"))
+                        qual_overwrite(&t->product, cp);
+                    else if (!strcmp(key, "ro.product.model"))
+                        qual_overwrite(&t->model, cp);
+                    else if (!strcmp(key, "ro.product.device"))
+                        qual_overwrite(&t->device, cp);
+                }
+                key = adb_strtok_r(NULL, prop_seps, &save);
+            }
+        }
     }
 
     if(!strcmp(type, "bootloader")){
@@ -1068,7 +1129,7 @@
 
     strncpy(hostbuf, host, sizeof(hostbuf) - 1);
     if (portstr) {
-        if (portstr - host >= sizeof(hostbuf)) {
+        if (portstr - host >= (ptrdiff_t)sizeof(hostbuf)) {
             snprintf(buffer, buffer_size, "bad host name %s", host);
             return;
         }
diff --git a/adb/adb.h b/adb/adb.h
index 815e4e6..df88896 100644
--- a/adb/adb.h
+++ b/adb/adb.h
@@ -190,6 +190,8 @@
         /* used to identify transports for clients */
     char *serial;
     char *product;
+    char *model;
+    char *device;
     char *devpath;
     int adb_port; // Use for emulators (local transport)
 
@@ -254,7 +256,7 @@
 ** get_device_transport does an acquire on your behalf before returning
 */
 void init_transport_registration(void);
-int  list_transports(char *buf, size_t  bufsize, int show_devpath);
+int  list_transports(char *buf, size_t  bufsize, int long_listing);
 void update_transports(void);
 
 asocket*  create_device_tracker(void);
diff --git a/adb/commandline.c b/adb/commandline.c
index a6f1ce2..10b2332 100644
--- a/adb/commandline.c
+++ b/adb/commandline.c
@@ -85,7 +85,7 @@
         " -e                            - directs command to the only running emulator.\n"
         "                                 returns an error if more than one emulator is running.\n"
         " -s <specific device>          - directs command to the device or emulator with the given\n"
-        "                                 serial number or device path. Overrides ANDROID_SERIAL\n"
+        "                                 serial number or qualifier. Overrides ANDROID_SERIAL\n"
         "                                 environment variable.\n"
         " -p <product name or path>     - simple product name like 'sooner', or\n"
         "                                 a relative/absolute path to a product\n"
@@ -94,7 +94,7 @@
         "                                 environment variable is used, which must\n"
         "                                 be an absolute path.\n"
         " devices [-l]                  - list all connected devices\n"
-        "                                 ('-l' means list device paths)\n"
+        "                                 ('-l' will also list device qualifiers)\n"
         " connect <host>[:<port>]       - connect to a device via TCP/IP\n"
         "                                 Port 5555 is used by default if no port number is specified.\n"
         " disconnect [<host>[:<port>]]  - disconnect from a TCP/IP device.\n"
diff --git a/adb/protocol.txt b/adb/protocol.txt
index 398d042..abd63f9 100644
--- a/adb/protocol.txt
+++ b/adb/protocol.txt
@@ -72,7 +72,7 @@
 The system identity string should be "<systemtype>:<serialno>:<banner>"
 where systemtype is "bootloader", "device", or "host", serialno is some
 kind of unique ID (or empty), and banner is a human-readable version
-or identifier string (informational only).
+or identifier string.  The banner is used to transmit useful properties.
 
 
 --- OPEN(local-id, 0, "destination") -----------------------------------
diff --git a/adb/sockets.c b/adb/sockets.c
index 91db951..b77c38c 100644
--- a/adb/sockets.c
+++ b/adb/sockets.c
@@ -608,12 +608,30 @@
     return n;
 }
 
+#define PREFIX(str) { str, sizeof(str) - 1 }
+static const struct prefix_struct {
+    const char *str;
+    const size_t len;
+} prefixes[] = {
+    PREFIX("usb:"),
+    PREFIX("product:"),
+    PREFIX("model:"),
+    PREFIX("device:"),
+};
+static const int num_prefixes = (sizeof(prefixes) / sizeof(prefixes[0]));
+
 /* skip_host_serial return the position in a string
    skipping over the 'serial' parameter in the ADB protocol,
    where parameter string may be a host:port string containing
    the protocol delimiter (colon). */
 char *skip_host_serial(char *service) {
     char *first_colon, *serial_end;
+    int i;
+
+    for (i = 0; i < num_prefixes; i++) {
+        if (!strncmp(service, prefixes[i].str, prefixes[i].len))
+            return strchr(service + prefixes[i].len, ':');
+    }
 
     first_colon = strchr(service, ':');
     if (!first_colon) {
diff --git a/adb/sysdeps.h b/adb/sysdeps.h
index b518076..605fa17 100644
--- a/adb/sysdeps.h
+++ b/adb/sysdeps.h
@@ -254,6 +254,8 @@
     return isalpha(path[0]) && path[1] == ':' && path[2] == '\\';
 }
 
+extern char*  adb_strtok_r(char *str, const char *delim, char **saveptr);
+
 #else /* !_WIN32 a.k.a. Unix */
 
 #include "fdevent.h"
@@ -490,6 +492,13 @@
     return path[0] == '/';
 }
 
+static __inline__ char*  adb_strtok_r(char *str, const char *delim, char **saveptr)
+{
+    return strtok_r(str, delim, saveptr);
+}
+#undef   strtok_r
+#define  strtok_r  ___xxx_strtok_r
+
 #endif /* !_WIN32 */
 
 #endif /* _ADB_SYSDEPS_H */
diff --git a/adb/sysdeps_win32.c b/adb/sysdeps_win32.c
index c426718..d41c42c 100644
--- a/adb/sysdeps_win32.c
+++ b/adb/sysdeps_win32.c
@@ -2140,3 +2140,81 @@
     InitializeCriticalSection( &_win32_lock );
 }
 
+/* Windows doesn't have strtok_r.  Use the one from bionic. */
+
+/*
+ * Copyright (c) 1988 Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+char *
+adb_strtok_r(char *s, const char *delim, char **last)
+{
+	char *spanp;
+	int c, sc;
+	char *tok;
+
+
+	if (s == NULL && (s = *last) == NULL)
+		return (NULL);
+
+	/*
+	 * Skip (span) leading delimiters (s += strspn(s, delim), sort of).
+	 */
+cont:
+	c = *s++;
+	for (spanp = (char *)delim; (sc = *spanp++) != 0;) {
+		if (c == sc)
+			goto cont;
+	}
+
+	if (c == 0) {		/* no non-delimiter characters */
+		*last = NULL;
+		return (NULL);
+	}
+	tok = s - 1;
+
+	/*
+	 * Scan token (scan for delimiters: s += strcspn(s, delim), sort of).
+	 * Note that delim must have one NUL; we stop if we see that, too.
+	 */
+	for (;;) {
+		c = *s++;
+		spanp = (char *)delim;
+		do {
+			if ((sc = *spanp++) == c) {
+				if (c == 0)
+					s = NULL;
+				else
+					s[-1] = 0;
+				*last = s;
+				return (tok);
+			}
+		} while (sc != 0);
+	}
+	/* NOTREACHED */
+}
diff --git a/adb/transport.c b/adb/transport.c
index 29cb582..9fd6cc2 100644
--- a/adb/transport.c
+++ b/adb/transport.c
@@ -601,6 +601,10 @@
             free(t->product);
         if (t->serial)
             free(t->serial);
+        if (t->model)
+            free(t->model);
+        if (t->device)
+            free(t->device);
         if (t->devpath)
             free(t->devpath);
 
@@ -739,6 +743,45 @@
     dis->next = dis->prev = dis;
 }
 
+static int qual_char_is_invalid(char ch)
+{
+    if ('A' <= ch && ch <= 'Z')
+        return 0;
+    if ('a' <= ch && ch <= 'z')
+        return 0;
+    if ('0' <= ch && ch <= '9')
+        return 0;
+    return 1;
+}
+
+static int qual_match(const char *to_test,
+                      const char *prefix, const char *qual, int sanitize_qual)
+{
+    if (!to_test || !*to_test)
+        /* Return true if both the qual and to_test are null strings. */
+        return !qual || !*qual;
+
+    if (!qual)
+        return 0;
+
+    if (prefix) {
+        while (*prefix) {
+            if (*prefix++ != *to_test++)
+                return 0;
+        }
+    }
+
+    while (*qual) {
+        char ch = *qual++;
+        if (sanitize_qual && qual_char_is_invalid(ch))
+            ch = '_';
+        if (ch != *to_test++)
+            return 0;
+    }
+
+    /* Everything matched so far.  Return true if *to_test is a NUL. */
+    return !*to_test;
+}
 
 atransport *acquire_one_transport(int state, transport_type ttype, const char* serial, char** error_out)
 {
@@ -760,13 +803,19 @@
 
         /* check for matching serial number */
         if (serial) {
-            if (t->serial && !strcmp(serial, t->serial)) {
+            if ((t->serial && !strcmp(serial, t->serial)) ||
+                (t->devpath && !strcmp(serial, t->devpath)) ||
+                qual_match(serial, "product:", t->product, 0) ||
+                qual_match(serial, "model:", t->model, 1) ||
+                qual_match(serial, "device:", t->device, 0)) {
+                if (result) {
+                    if (error_out)
+                        *error_out = "more than one device";
+                    ambiguous = 1;
+                    result = NULL;
+                    break;
+                }
                 result = t;
-                break;
-            }
-            if (t->devpath && !strcmp(serial, t->devpath)) {
-                result = t;
-                break;
             }
         } else {
             if (ttype == kTransportUsb && t->type == kTransportUsb) {
@@ -843,7 +892,58 @@
     }
 }
 
-int list_transports(char *buf, size_t  bufsize, int show_devpath)
+static void add_qual(char **buf, size_t *buf_size,
+                     const char *prefix, const char *qual, int sanitize_qual)
+{
+    size_t len;
+    int prefix_len;
+
+    if (!buf || !*buf || !buf_size || !*buf_size || !qual || !*qual)
+        return;
+
+    len = snprintf(*buf, *buf_size, "%s%n%s", prefix, &prefix_len, qual);
+
+    if (sanitize_qual) {
+        char *cp;
+        for (cp = *buf + prefix_len; cp < *buf + len; cp++) {
+            if (qual_char_is_invalid(*cp))
+                *cp = '_';
+        }
+    }
+
+    *buf_size -= len;
+    *buf += len;
+}
+
+static size_t format_transport(atransport *t, char *buf, size_t bufsize,
+                               int long_listing)
+{
+    const char* serial = t->serial;
+    if (!serial || !serial[0])
+        serial = "????????????";
+
+    if (!long_listing) {
+        return snprintf(buf, bufsize, "%s\t%s\n", serial, statename(t));
+    } else {
+        size_t len, remaining = bufsize;
+
+        len = snprintf(buf, remaining, "%-22s %s", serial, statename(t));
+        remaining -= len;
+        buf += len;
+
+        add_qual(&buf, &remaining, " ", t->devpath, 0);
+        add_qual(&buf, &remaining, " product:", t->product, 0);
+        add_qual(&buf, &remaining, " model:", t->model, 1);
+        add_qual(&buf, &remaining, " device:", t->device, 0);
+
+        len = snprintf(buf, remaining, "\n");
+        remaining -= len;
+
+        return bufsize - remaining;
+    }
+}
+
+int list_transports(char *buf, size_t  bufsize, int long_listing)
 {
     char*       p   = buf;
     char*       end = buf + bufsize;
@@ -853,17 +953,7 @@
         /* XXX OVERRUN PROBLEMS XXX */
     adb_mutex_lock(&transport_lock);
     for(t = transport_list.next; t != &transport_list; t = t->next) {
-        const char* serial = t->serial;
-        if (!serial || !serial[0])
-            serial = "????????????";
-        if (show_devpath) {
-            const char* devpath = t->devpath;
-            if (!devpath || !devpath[0])
-                devpath = "????????????";
-            len = snprintf(p, end - p, "%s\t%s\t%s\n", serial, devpath, statename(t));
-        } else
-            len = snprintf(p, end - p, "%s\t%s\n", serial, statename(t));
-
+        len = format_transport(t, p, end - p, long_listing);
         if (p + len >= end) {
             /* discard last line if buffer is too short */
             break;
@@ -923,9 +1013,6 @@
         if (t->serial && !strcmp(serial, t->serial)) {
             break;
         }
-        if (t->devpath && !strcmp(serial, t->devpath)) {
-            break;
-        }
      }
     adb_mutex_unlock(&transport_lock);
 
diff --git a/fastboot/fastboot.c b/fastboot/fastboot.c
index 5858e23..c44f937 100644
--- a/fastboot/fastboot.c
+++ b/fastboot/fastboot.c
@@ -185,15 +185,13 @@
         if (!serial[0]) {
             serial = "????????????";
         }
+        // output compatible with "adb devices"
         if (!long_listing) {
-            // output compatible with "adb devices"
             printf("%s\tfastboot\n", serial);
+        } else if (!info->device_path) {
+            printf("%-22s fastboot\n", serial);
         } else {
-            char* device_path = info->device_path;
-            if (!device_path[0]) {
-                device_path = "????????????";
-            }
-            printf("%s\t%s\tfastboot\n", serial, device_path);
+            printf("%-22s fastboot %s\n", serial, info->device_path);
         }
     }
 
diff --git a/libcutils/sched_policy.c b/libcutils/sched_policy.c
index e03ce00..d20d217 100644
--- a/libcutils/sched_policy.c
+++ b/libcutils/sched_policy.c
@@ -63,8 +63,6 @@
 #if CAN_SET_SP_SYSTEM
 static int system_cgroup_fd = -1;
 #endif
-static int audio_app_cgroup_fd = -1;
-static int audio_sys_cgroup_fd = -1;
 
 /* Add tid to the scheduling group defined by the policy */
 static int add_tid_to_cgroup(int tid, SchedPolicy policy)
@@ -76,6 +74,8 @@
         fd = bg_cgroup_fd;
         break;
     case SP_FOREGROUND:
+    case SP_AUDIO_APP:
+    case SP_AUDIO_SYS:
         fd = fg_cgroup_fd;
         break;
 #if CAN_SET_SP_SYSTEM
@@ -83,12 +83,6 @@
         fd = system_cgroup_fd;
         break;
 #endif
-    case SP_AUDIO_APP:
-        fd = audio_app_cgroup_fd;
-        break;
-    case SP_AUDIO_SYS:
-        fd = audio_sys_cgroup_fd;
-        break;
     default:
         fd = -1;
         break;
@@ -137,30 +131,17 @@
         }
 #endif
 
-        filename = "/dev/cpuctl/foreground/tasks";
+        filename = "/dev/cpuctl/apps/tasks";
         fg_cgroup_fd = open(filename, O_WRONLY | O_CLOEXEC);
         if (fg_cgroup_fd < 0) {
             SLOGE("open of %s failed: %s\n", filename, strerror(errno));
         }
 
-        filename = "/dev/cpuctl/bg_non_interactive/tasks";
+        filename = "/dev/cpuctl/apps/bg_non_interactive/tasks";
         bg_cgroup_fd = open(filename, O_WRONLY | O_CLOEXEC);
         if (bg_cgroup_fd < 0) {
             SLOGE("open of %s failed: %s\n", filename, strerror(errno));
         }
-
-        filename = "/dev/cpuctl/audio_app/tasks";
-        audio_app_cgroup_fd = open(filename, O_WRONLY | O_CLOEXEC);
-        if (audio_app_cgroup_fd < 0) {
-            SLOGV("open of %s failed: %s\n", filename, strerror(errno));
-        }
-
-        filename = "/dev/cpuctl/audio_sys/tasks";
-        audio_sys_cgroup_fd = open(filename, O_WRONLY | O_CLOEXEC);
-        if (audio_sys_cgroup_fd < 0) {
-            SLOGV("open of %s failed: %s\n", filename, strerror(errno));
-        }
-
     } else {
         __sys_supports_schedgroups = 0;
     }
@@ -253,14 +234,10 @@
             return -1;
         if (grpBuf[0] == '\0') {
             *policy = SP_SYSTEM;
-        } else if (!strcmp(grpBuf, "bg_non_interactive")) {
+        } else if (!strcmp(grpBuf, "apps/bg_non_interactive")) {
             *policy = SP_BACKGROUND;
-        } else if (!strcmp(grpBuf, "foreground")) {
+        } else if (!strcmp(grpBuf, "apps")) {
             *policy = SP_FOREGROUND;
-        } else if (!strcmp(grpBuf, "audio_app")) {
-            *policy = SP_AUDIO_APP;
-        } else if (!strcmp(grpBuf, "audio_sys")) {
-            *policy = SP_AUDIO_SYS;
         } else {
             errno = ERANGE;
             return -1;
@@ -319,17 +296,13 @@
         SLOGD("vvv tid %d (%s)", tid, thread_name);
         break;
     case SP_FOREGROUND:
+    case SP_AUDIO_APP:
+    case SP_AUDIO_SYS:
         SLOGD("^^^ tid %d (%s)", tid, thread_name);
         break;
     case SP_SYSTEM:
         SLOGD("/// tid %d (%s)", tid, thread_name);
         break;
-    case SP_AUDIO_APP:
-        SLOGD("aaa tid %d (%s)", tid, thread_name);
-        break;
-    case SP_AUDIO_SYS:
-        SLOGD("sss tid %d (%s)", tid, thread_name);
-        break;
     default:
         SLOGD("??? tid %d (%s)", tid, thread_name);
         break;
diff --git a/rootdir/init.rc b/rootdir/init.rc
index aa1bb92..2305572 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -93,34 +93,20 @@
     write /dev/cpuctl/cpu.rt_runtime_us 950000
     write /dev/cpuctl/cpu.rt_period_us 1000000
 
-    mkdir /dev/cpuctl/foreground
-    chown system system /dev/cpuctl/foreground/tasks
-    chmod 0666 /dev/cpuctl/foreground/tasks
-    write /dev/cpuctl/foreground/cpu.shares 1024
-    write /dev/cpuctl/foreground/cpu.rt_runtime_us 100000
-    write /dev/cpuctl/foreground/cpu.rt_period_us 1000000
+    mkdir /dev/cpuctl/apps
+    chown system system /dev/cpuctl/apps/tasks
+    chmod 0666 /dev/cpuctl/apps/tasks
+    write /dev/cpuctl/apps/cpu.shares 1024
+    write /dev/cpuctl/apps/cpu.rt_runtime_us 800000
+    write /dev/cpuctl/apps/cpu.rt_period_us 1000000
 
-    mkdir /dev/cpuctl/bg_non_interactive
-    chown system system /dev/cpuctl/bg_non_interactive/tasks
-    chmod 0666 /dev/cpuctl/bg_non_interactive/tasks
+    mkdir /dev/cpuctl/apps/bg_non_interactive
+    chown system system /dev/cpuctl/apps/bg_non_interactive/tasks
+    chmod 0666 /dev/cpuctl/apps/bg_non_interactive/tasks
     # 5.0 %
-    write /dev/cpuctl/bg_non_interactive/cpu.shares 52
-    write /dev/cpuctl/bg_non_interactive/cpu.rt_runtime_us 100000
-    write /dev/cpuctl/bg_non_interactive/cpu.rt_period_us 1000000
-
-    mkdir /dev/cpuctl/audio_app
-    chown system system /dev/cpuctl/audio_app/tasks
-    chmod 0660 /dev/cpuctl/audio_app/tasks
-    write /dev/cpuctl/audio_app/cpu.shares 10
-    write /dev/cpuctl/audio_app/cpu.rt_runtime_us 100000
-    write /dev/cpuctl/audio_app/cpu.rt_period_us 1000000
-
-    mkdir /dev/cpuctl/audio_sys
-    chown system system /dev/cpuctl/audio_sys/tasks
-    chmod 0660 /dev/cpuctl/audio_sys/tasks
-    write /dev/cpuctl/audio_sys/cpu.shares 10
-    write /dev/cpuctl/audio_sys/cpu.rt_runtime_us 100000
-    write /dev/cpuctl/audio_sys/cpu.rt_period_us 1000000
+    write /dev/cpuctl/apps/bg_non_interactive/cpu.shares 52
+    write /dev/cpuctl/apps/bg_non_interactive/cpu.rt_runtime_us 700000
+    write /dev/cpuctl/apps/bg_non_interactive/cpu.rt_period_us 1000000
 
 # Allow everybody to read the xt_qtaguid resource tracking misc dev.
 # This is needed by any process that uses socket tagging.
diff --git a/sdcard/Android.mk b/sdcard/Android.mk
index c430ac8..fb04d6d 100644
--- a/sdcard/Android.mk
+++ b/sdcard/Android.mk
@@ -4,6 +4,7 @@
 
 LOCAL_SRC_FILES:= sdcard.c
 LOCAL_MODULE:= sdcard
+LOCAL_CFLAGS := -Wall -Wno-unused-parameter
 
 LOCAL_SHARED_LIBRARIES := libc
 
diff --git a/sdcard/sdcard.c b/sdcard/sdcard.c
index a95513c..316588c 100644
--- a/sdcard/sdcard.c
+++ b/sdcard/sdcard.c
@@ -25,7 +25,9 @@
 #include <sys/statfs.h>
 #include <sys/uio.h>
 #include <dirent.h>
+#include <limits.h>
 #include <ctype.h>
+#include <pthread.h>
 
 #include <private/android_filesystem_config.h>
 
@@ -72,28 +74,42 @@
 
 #define MOUNT_POINT "/storage/sdcard0"
 
+/* Maximum number of bytes to write in one request. */
+#define MAX_WRITE (256 * 1024)
+
+/* Maximum number of bytes to read in one request. */
+#define MAX_READ (128 * 1024)
+
+/* Largest possible request.
+ * The request size is bounded by the maximum size of a FUSE_WRITE request because it has
+ * the largest possible data payload. */
+#define MAX_REQUEST_SIZE (sizeof(struct fuse_in_header) + sizeof(struct fuse_write_in) + MAX_WRITE)
+
+/* Default number of threads. */
+#define DEFAULT_NUM_THREADS 2
+
+/* Pseudo-error constant used to indicate that no fuse status is needed
+ * or that a reply has already been written. */
+#define NO_STATUS 1
+
 struct handle {
-    struct node *node;
     int fd;
 };
 
 struct dirhandle {
-    struct node *node;
     DIR *d;
 };
 
 struct node {
+    __u32 refcount;
     __u64 nid;
     __u64 gen;
 
     struct node *next;          /* per-dir sibling list */
     struct node *child;         /* first contained file by this dir */
-    struct node *all;           /* global node list */
     struct node *parent;        /* containing directory */
 
-    __u32 refcount;
-    __u32 namelen;
-
+    size_t namelen;
     char *name;
     /* If non-null, this is the real name of the file in the underlying storage.
      * This may differ from the field "name" only by case.
@@ -103,98 +119,166 @@
     char *actual_name;
 };
 
+/* Global data structure shared by all fuse handlers. */
 struct fuse {
+    pthread_mutex_t lock;
+
     __u64 next_generation;
-    __u64 next_node_id;
-
     int fd;
-
-    struct node *all;
-
     struct node root;
-    char rootpath[1024];
+    char rootpath[PATH_MAX];
 };
 
-static unsigned uid = -1;
-static unsigned gid = -1;
+/* Private data used by a single fuse handler. */
+struct fuse_handler {
+    struct fuse* fuse;
+    int token;
 
-#define PATH_BUFFER_SIZE 1024
+    /* To save memory, we never use the contents of the request buffer and the read
+     * buffer at the same time.  This allows us to share the underlying storage. */
+    union {
+        __u8 request_buffer[MAX_REQUEST_SIZE];
+        __u8 read_buffer[MAX_READ];
+    };
+};
 
-#define NO_CASE_SENSITIVE_MATCH 0
-#define CASE_SENSITIVE_MATCH 1
-
-/*
- * Get the real-life absolute path to a node.
- *   node: start at this node
- *   buf: storage for returned string
- *   name: append this string to path if set
- */
-char *do_node_get_path(struct node *node, char *buf, const char *name, int match_case_insensitive)
+static inline void *id_to_ptr(__u64 nid)
 {
-    struct node *in_node = node;
-    const char *in_name = name;
-    char *out = buf + PATH_BUFFER_SIZE - 1;
-    int len;
-    out[0] = 0;
+    return (void *) (uintptr_t) nid;
+}
 
-    if (name) {
-        len = strlen(name);
-        goto start;
-    }
+static inline __u64 ptr_to_id(void *ptr)
+{
+    return (__u64) (uintptr_t) ptr;
+}
 
-    while (node) {
-        name = (node->actual_name ? node->actual_name : node->name);
-        len = node->namelen;
-        node = node->parent;
-    start:
-        if ((len + 1) > (out - buf))
-            return 0;
-        out -= len;
-        memcpy(out, name, len);
-        /* avoid double slash at beginning of path */
-        if (out[0] != '/') {
-            out --;
-            out[0] = '/';
+static void acquire_node_locked(struct node* node)
+{
+    node->refcount++;
+    TRACE("ACQUIRE %p (%s) rc=%d\n", node, node->name, node->refcount);
+}
+
+static void remove_node_from_parent_locked(struct node* node);
+
+static void release_node_locked(struct node* node)
+{
+    TRACE("RELEASE %p (%s) rc=%d\n", node, node->name, node->refcount);
+    if (node->refcount > 0) {
+        node->refcount--;
+        if (!node->refcount) {
+            TRACE("DESTROY %p (%s)\n", node, node->name);
+            remove_node_from_parent_locked(node);
+
+                /* TODO: remove debugging - poison memory */
+            memset(node->name, 0xef, node->namelen);
+            free(node->name);
+            free(node->actual_name);
+            memset(node, 0xfc, sizeof(*node));
+            free(node);
         }
+    } else {
+        ERROR("Zero refcnt %p\n", node);
+    }
+}
+
+static void add_node_to_parent_locked(struct node *node, struct node *parent) {
+    node->parent = parent;
+    node->next = parent->child;
+    parent->child = node;
+    acquire_node_locked(parent);
+}
+
+static void remove_node_from_parent_locked(struct node* node)
+{
+    if (node->parent) {
+        if (node->parent->child == node) {
+            node->parent->child = node->parent->child->next;
+        } else {
+            struct node *node2;
+            node2 = node->parent->child;
+            while (node2->next != node)
+                node2 = node2->next;
+            node2->next = node->next;
+        }
+        release_node_locked(node->parent);
+        node->parent = NULL;
+        node->next = NULL;
+    }
+}
+
+/* Gets the absolute path to a node into the provided buffer.
+ *
+ * Populates 'buf' with the path and returns the length of the path on success,
+ * or returns -1 if the path is too long for the provided buffer.
+ */
+static ssize_t get_node_path_locked(struct node* node, char* buf, size_t bufsize)
+{
+    size_t namelen = node->namelen;
+    if (bufsize < namelen + 1) {
+        return -1;
     }
 
-    /* If we are searching for a file within node (rather than computing node's path)
-     * and fail, then we need to look for a case insensitive match.
-     */
-    if (in_name && match_case_insensitive && access(out, F_OK) != 0) {
-        char *path, buffer[PATH_BUFFER_SIZE];
-        DIR* dir;
+    ssize_t pathlen = 0;
+    if (node->parent) {
+        pathlen = get_node_path_locked(node->parent, buf, bufsize - namelen - 2);
+        if (pathlen < 0) {
+            return -1;
+        }
+        buf[pathlen++] = '/';
+    }
+
+    const char* name = node->actual_name ? node->actual_name : node->name;
+    memcpy(buf + pathlen, name, namelen + 1); /* include trailing \0 */
+    return pathlen + namelen;
+}
+
+/* Finds the absolute path of a file within a given directory.
+ * Performs a case-insensitive search for the file and sets the buffer to the path
+ * of the first matching file.  If 'search' is zero or if no match is found, sets
+ * the buffer to the path that the file would have, assuming the name were case-sensitive.
+ *
+ * Populates 'buf' with the path and returns the actual name (within 'buf') on success,
+ * or returns NULL if the path is too long for the provided buffer.
+ */
+static char* find_file_within(const char* path, const char* name,
+        char* buf, size_t bufsize, int search)
+{
+    size_t pathlen = strlen(path);
+    size_t namelen = strlen(name);
+    size_t childlen = pathlen + namelen + 1;
+    char* actual;
+
+    if (bufsize <= childlen) {
+        return NULL;
+    }
+
+    memcpy(buf, path, pathlen);
+    buf[pathlen] = '/';
+    actual = buf + pathlen + 1;
+    memcpy(actual, name, namelen + 1);
+
+    if (search && access(buf, F_OK)) {
         struct dirent* entry;
-        path = do_node_get_path(in_node, buffer, NULL, NO_CASE_SENSITIVE_MATCH);
-        dir = opendir(path);
+        DIR* dir = opendir(path);
         if (!dir) {
             ERROR("opendir %s failed: %s", path, strerror(errno));
-            return out;
+            return actual;
         }
-
         while ((entry = readdir(dir))) {
-            if (!strcasecmp(entry->d_name, in_name)) {
-                /* we have a match - replace the name */
-                len = strlen(in_name);
-                memcpy(buf + PATH_BUFFER_SIZE - len - 1, entry->d_name, len);
+            if (!strcasecmp(entry->d_name, name)) {
+                /* we have a match - replace the name, don't need to copy the null again */
+                memcpy(actual, entry->d_name, namelen);
                 break;
             }
         }
         closedir(dir);
     }
-
-   return out;
+    return actual;
 }
 
-char *node_get_path(struct node *node, char *buf, const char *name)
+static void attr_from_stat(struct fuse_attr *attr, const struct stat *s, __u64 nid)
 {
-    /* We look for case insensitive matches by default */
-    return do_node_get_path(node, buf, name, CASE_SENSITIVE_MATCH);
-}
-
-void attr_from_stat(struct fuse_attr *attr, struct stat *s)
-{
-    attr->ino = s->st_ino;
+    attr->ino = nid;
     attr->size = s->st_size;
     attr->blocks = s->st_blocks;
     attr->atime = s->st_atime;
@@ -221,129 +305,81 @@
     attr->gid = AID_SDCARD_RW;
 }
 
-int node_get_attr(struct node *node, struct fuse_attr *attr)
-{
-    int res;
-    struct stat s;
-    char *path, buffer[PATH_BUFFER_SIZE];
-
-    path = node_get_path(node, buffer, 0);
-    res = lstat(path, &s);
-    if (res < 0) {
-        ERROR("lstat('%s') errno %d\n", path, errno);
-        return -1;
-    }
-
-    attr_from_stat(attr, &s);    
-    attr->ino = node->nid;
-
-    return 0;
-}
-
-static void add_node_to_parent(struct node *node, struct node *parent) {
-    node->parent = parent;
-    node->next = parent->child;
-    parent->child = node;
-    parent->refcount++;
-}
-
-/* Check to see if our parent directory already has a file with a name
- * that differs only by case.  If we find one, store it in the actual_name
- * field so node_get_path will map it to this file in the underlying storage.
- */
-static void node_find_actual_name(struct node *node)
-{
-    char *path, buffer[PATH_BUFFER_SIZE];
-    const char *node_name = node->name;
-    DIR* dir;
-    struct dirent* entry;
-
-    if (!node->parent) return;
-
-    path = node_get_path(node->parent, buffer, 0);
-    dir = opendir(path);
-    if (!dir) {
-        ERROR("opendir %s failed: %s", path, strerror(errno));
-        return;
-    }
-
-    while ((entry = readdir(dir))) {
-        const char *test_name = entry->d_name;
-        if (strcmp(test_name, node_name) && !strcasecmp(test_name, node_name)) {
-            /* we have a match - differs but only by case */
-            node->actual_name = strdup(test_name);
-            if (!node->actual_name) {
-                ERROR("strdup failed - out of memory\n");
-                exit(1);
-            }
-            break;
-        }
-    }
-    closedir(dir);
-}
-
-struct node *node_create(struct node *parent, const char *name, __u64 nid, __u64 gen)
+struct node *create_node_locked(struct fuse* fuse,
+        struct node *parent, const char *name, const char* actual_name)
 {
     struct node *node;
-    int namelen = strlen(name);
+    size_t namelen = strlen(name);
 
     node = calloc(1, sizeof(struct node));
-    if (node == 0) {
-        return 0;
+    if (!node) {
+        return NULL;
     }
     node->name = malloc(namelen + 1);
-    if (node->name == 0) {
+    if (!node->name) {
         free(node);
-        return 0;
+        return NULL;
     }
-
-    node->nid = nid;
-    node->gen = gen;
-    add_node_to_parent(node, parent);
     memcpy(node->name, name, namelen + 1);
+    if (strcmp(name, actual_name)) {
+        node->actual_name = malloc(namelen + 1);
+        if (!node->actual_name) {
+            free(node->name);
+            free(node);
+            return NULL;
+        }
+        memcpy(node->actual_name, actual_name, namelen + 1);
+    }
     node->namelen = namelen;
-    node_find_actual_name(node);
+    node->nid = ptr_to_id(node);
+    node->gen = fuse->next_generation++;
+    acquire_node_locked(node);
+    add_node_to_parent_locked(node, parent);
     return node;
 }
 
-static char *rename_node(struct node *node, const char *name)
+static int rename_node_locked(struct node *node, const char *name,
+        const char* actual_name)
 {
-    node->namelen = strlen(name);
-    char *newname = realloc(node->name, node->namelen + 1);
-    if (newname == 0)
-        return 0;
-    node->name = newname;
-    memcpy(node->name, name, node->namelen + 1);
-    node_find_actual_name(node);
-    return node->name;
+    size_t namelen = strlen(name);
+    int need_actual_name = strcmp(name, actual_name);
+
+    /* make the storage bigger without actually changing the name
+     * in case an error occurs part way */
+    if (namelen > node->namelen) {
+        char* new_name = realloc(node->name, namelen + 1);
+        if (!new_name) {
+            return -ENOMEM;
+        }
+        node->name = new_name;
+        if (need_actual_name && node->actual_name) {
+            char* new_actual_name = realloc(node->actual_name, namelen + 1);
+            if (!new_actual_name) {
+                return -ENOMEM;
+            }
+            node->actual_name = new_actual_name;
+        }
+    }
+
+    /* update the name, taking care to allocate storage before overwriting the old name */
+    if (need_actual_name) {
+        if (!node->actual_name) {
+            node->actual_name = malloc(namelen + 1);
+            if (!node->actual_name) {
+                return -ENOMEM;
+            }
+        }
+        memcpy(node->actual_name, actual_name, namelen + 1);
+    } else {
+        free(node->actual_name);
+        node->actual_name = NULL;
+    }
+    memcpy(node->name, name, namelen + 1);
+    node->namelen = namelen;
+    return 0;
 }
 
-void fuse_init(struct fuse *fuse, int fd, const char *path)
-{
-    fuse->fd = fd;
-    fuse->next_node_id = 2;
-    fuse->next_generation = 0;
-
-    fuse->all = &fuse->root;
-
-    memset(&fuse->root, 0, sizeof(fuse->root));
-    fuse->root.nid = FUSE_ROOT_ID; /* 1 */
-    fuse->root.refcount = 2;
-    rename_node(&fuse->root, path);
-}
-
-static inline void *id_to_ptr(__u64 nid)
-{
-    return (void *) nid;
-}
-
-static inline __u64 ptr_to_id(void *ptr)
-{
-    return (__u64) ptr;
-}
-
-
-struct node *lookup_by_inode(struct fuse *fuse, __u64 nid)
+static struct node *lookup_node_by_id_locked(struct fuse *fuse, __u64 nid)
 {
     if (nid == FUSE_ROOT_ID) {
         return &fuse->root;
@@ -352,9 +388,23 @@
     }
 }
 
-struct node *lookup_child_by_name(struct node *node, const char *name)
+static struct node* lookup_node_and_path_by_id_locked(struct fuse* fuse, __u64 nid,
+        char* buf, size_t bufsize)
+{
+    struct node* node = lookup_node_by_id_locked(fuse, nid);
+    if (node && get_node_path_locked(node, buf, bufsize) < 0) {
+        node = NULL;
+    }
+    return node;
+}
+
+static struct node *lookup_child_by_name_locked(struct node *node, const char *name)
 {
     for (node = node->child; node; node = node->next) {
+        /* use exact string comparison, nodes that differ by case
+         * must be considered distinct even if they refer to the same
+         * underlying file as otherwise operations such as "mv x x"
+         * will not work because the source and target nodes are the same. */
         if (!strcmp(name, node->name)) {
             return node;
         }
@@ -362,123 +412,43 @@
     return 0;
 }
 
-struct node *lookup_child_by_inode(struct node *node, __u64 nid)
+static struct node* acquire_or_create_child_locked(
+        struct fuse* fuse, struct node* parent,
+        const char* name, const char* actual_name)
 {
-    for (node = node->child; node; node = node->next) {
-        if (node->nid == nid) {
-            return node;
-        }
-    }
-    return 0;
-}
-
-static void dec_refcount(struct node *node) {
-    if (node->refcount > 0) {
-        node->refcount--;
-        TRACE("dec_refcount %p(%s) -> %d\n", node, node->name, node->refcount);
+    struct node* child = lookup_child_by_name_locked(parent, name);
+    if (child) {
+        acquire_node_locked(child);
     } else {
-        ERROR("Zero refcnt %p\n", node);
+        child = create_node_locked(fuse, parent, name, actual_name);
     }
- }
-
-static struct node *remove_child(struct node *parent, __u64 nid)
-{
-    struct node *prev = 0;
-    struct node *node;
-
-    for (node = parent->child; node; node = node->next) {
-        if (node->nid == nid) {
-            if (prev) {
-                prev->next = node->next;
-            } else {
-                parent->child = node->next;
-            }
-            node->next = 0;
-            node->parent = 0;
-            dec_refcount(parent);
-            return node;
-        }
-        prev = node;
-    }
-    return 0;
+    return child;
 }
 
-struct node *node_lookup(struct fuse *fuse, struct node *parent, const char *name,
-                         struct fuse_attr *attr)
+static void fuse_init(struct fuse *fuse, int fd, const char *path)
 {
-    int res;
-    struct stat s;
-    char *path, buffer[PATH_BUFFER_SIZE];
-    struct node *node;
+    pthread_mutex_init(&fuse->lock, NULL);
 
-    path = node_get_path(parent, buffer, name);
-        /* XXX error? */
+    fuse->fd = fd;
+    fuse->next_generation = 0;
 
-    res = lstat(path, &s);
-    if (res < 0)
-        return 0;
-    
-    node = lookup_child_by_name(parent, name);
-    if (!node) {
-        node = node_create(parent, name, fuse->next_node_id++, fuse->next_generation++);
-        if (!node)
-            return 0;
-        node->nid = ptr_to_id(node);
-        node->all = fuse->all;
-        fuse->all = node;
-    }
-
-    attr_from_stat(attr, &s);
-    attr->ino = node->nid;
-
-    return node;
+    memset(&fuse->root, 0, sizeof(fuse->root));
+    fuse->root.nid = FUSE_ROOT_ID; /* 1 */
+    fuse->root.refcount = 2;
+    fuse->root.namelen = strlen(path);
+    fuse->root.name = strdup(path);
 }
 
-void node_release(struct node *node)
-{
-    TRACE("RELEASE %p (%s) rc=%d\n", node, node->name, node->refcount);
-    dec_refcount(node);
-    if (node->refcount == 0) {
-        if (node->parent->child == node) {
-            node->parent->child = node->parent->child->next;
-        } else {
-            struct node *node2;
-
-            node2 = node->parent->child;
-            while (node2->next != node)
-                node2 = node2->next;
-            node2->next = node->next;            
-        }
-
-        TRACE("DESTROY %p (%s)\n", node, node->name);
-
-        node_release(node->parent);
-
-        node->parent = 0;
-        node->next = 0;
-
-            /* TODO: remove debugging - poison memory */
-        memset(node->name, 0xef, node->namelen);
-        free(node->name);
-        free(node->actual_name);
-        memset(node, 0xfc, sizeof(*node));
-        free(node);
-    }
-}
-
-void fuse_status(struct fuse *fuse, __u64 unique, int err)
+static void fuse_status(struct fuse *fuse, __u64 unique, int err)
 {
     struct fuse_out_header hdr;
     hdr.len = sizeof(hdr);
     hdr.error = err;
     hdr.unique = unique;
-    if (err) {
-//        ERROR("*** %d ***\n", err);
-    }
     write(fuse->fd, &hdr, sizeof(hdr));
 }
 
-void fuse_reply(struct fuse *fuse, __u64 unique, void *data, int len)
+static void fuse_reply(struct fuse *fuse, __u64 unique, void *data, int len)
 {
     struct fuse_out_header hdr;
     struct iovec vec[2];
@@ -499,481 +469,856 @@
     }
 }
 
-void lookup_entry(struct fuse *fuse, struct node *node,
-                  const char *name, __u64 unique)
+static int fuse_reply_entry(struct fuse* fuse, __u64 unique,
+        struct node* parent, const char* name, const char* actual_name,
+        const char* path)
 {
+    struct node* node;
     struct fuse_entry_out out;
-    
-    memset(&out, 0, sizeof(out));
+    struct stat s;
 
-    node = node_lookup(fuse, node, name, &out.attr);
-    if (!node) {
-        fuse_status(fuse, unique, -ENOENT);
-        return;
+    if (lstat(path, &s) < 0) {
+        return -errno;
     }
-    
-    node->refcount++;
-//    fprintf(stderr,"ACQUIRE %p (%s) rc=%d\n", node, node->name, node->refcount);
+
+    pthread_mutex_lock(&fuse->lock);
+    node = acquire_or_create_child_locked(fuse, parent, name, actual_name);
+    if (!node) {
+        pthread_mutex_unlock(&fuse->lock);
+        return -ENOMEM;
+    }
+    memset(&out, 0, sizeof(out));
+    attr_from_stat(&out.attr, &s, node->nid);
+    out.attr_valid = 10;
+    out.entry_valid = 10;
     out.nodeid = node->nid;
     out.generation = node->gen;
-    out.entry_valid = 10;
-    out.attr_valid = 10;
-    
+    pthread_mutex_unlock(&fuse->lock);
     fuse_reply(fuse, unique, &out, sizeof(out));
+    return NO_STATUS;
 }
 
-void handle_fuse_request(struct fuse *fuse, struct fuse_in_header *hdr, void *data, unsigned len)
+static int fuse_reply_attr(struct fuse* fuse, __u64 unique, __u64 nid,
+        const char* path)
 {
-    struct node *node;
+    struct fuse_attr_out out;
+    struct stat s;
 
-    if ((len < sizeof(*hdr)) || (hdr->len != len)) {
-        ERROR("malformed header\n");
-        return;
+    if (lstat(path, &s) < 0) {
+        return -errno;
     }
+    memset(&out, 0, sizeof(out));
+    attr_from_stat(&out.attr, &s, nid);
+    out.attr_valid = 10;
+    fuse_reply(fuse, unique, &out, sizeof(out));
+    return NO_STATUS;
+}
 
-    len -= hdr->len;
+static int handle_lookup(struct fuse* fuse, struct fuse_handler* handler,
+        const struct fuse_in_header *hdr, const char* name)
+{
+    struct node* parent_node;
+    char parent_path[PATH_MAX];
+    char child_path[PATH_MAX];
+    const char* actual_name;
 
-    if (hdr->nodeid) {
-        node = lookup_by_inode(fuse, hdr->nodeid);
-        if (!node) {
-            fuse_status(fuse, hdr->unique, -ENOENT);
-            return;
+    pthread_mutex_lock(&fuse->lock);
+    parent_node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid,
+            parent_path, sizeof(parent_path));
+    TRACE("[%d] LOOKUP %s @ %llx (%s)\n", handler->token, name, hdr->nodeid,
+        parent_node ? parent_node->name : "?");
+    pthread_mutex_unlock(&fuse->lock);
+
+    if (!parent_node || !(actual_name = find_file_within(parent_path, name,
+            child_path, sizeof(child_path), 1))) {
+        return -ENOENT;
+    }
+    return fuse_reply_entry(fuse, hdr->unique, parent_node, name, actual_name, child_path);
+}
+
+static int handle_forget(struct fuse* fuse, struct fuse_handler* handler,
+        const struct fuse_in_header *hdr, const struct fuse_forget_in *req)
+{
+    struct node* node;
+
+    pthread_mutex_lock(&fuse->lock);
+    node = lookup_node_by_id_locked(fuse, hdr->nodeid);
+    TRACE("[%d] FORGET #%lld @ %llx (%s)\n", handler->token, req->nlookup,
+            hdr->nodeid, node ? node->name : "?");
+    if (node) {
+        __u64 n = req->nlookup;
+        while (n--) {
+            release_node_locked(node);
         }
-    } else {
-        node = 0;
+    }
+    pthread_mutex_unlock(&fuse->lock);
+    return NO_STATUS; /* no reply */
+}
+
+static int handle_getattr(struct fuse* fuse, struct fuse_handler* handler,
+        const struct fuse_in_header *hdr, const struct fuse_getattr_in *req)
+{
+    struct node* node;
+    char path[PATH_MAX];
+
+    pthread_mutex_lock(&fuse->lock);
+    node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid, path, sizeof(path));
+    TRACE("[%d] GETATTR flags=%x fh=%llx @ %llx (%s)\n", handler->token,
+            req->getattr_flags, req->fh, hdr->nodeid, node ? node->name : "?");
+    pthread_mutex_unlock(&fuse->lock);
+
+    if (!node) {
+        return -ENOENT;
+    }
+    return fuse_reply_attr(fuse, hdr->unique, hdr->nodeid, path);
+}
+
+static int handle_setattr(struct fuse* fuse, struct fuse_handler* handler,
+        const struct fuse_in_header *hdr, const struct fuse_setattr_in *req)
+{
+    struct node* node;
+    char path[PATH_MAX];
+    struct timespec times[2];
+
+    pthread_mutex_lock(&fuse->lock);
+    node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid, path, sizeof(path));
+    TRACE("[%d] SETATTR fh=%llx valid=%x @ %llx (%s)\n", handler->token,
+            req->fh, req->valid, hdr->nodeid, node ? node->name : "?");
+    pthread_mutex_unlock(&fuse->lock);
+
+    if (!node) {
+        return -ENOENT;
     }
 
+    /* XXX: incomplete implementation on purpose.
+     * chmod/chown should NEVER be implemented.*/
+
+    if ((req->valid & FATTR_SIZE) && truncate(path, req->size) < 0) {
+        return -errno;
+    }
+
+    /* Handle changing atime and mtime.  If FATTR_ATIME_and FATTR_ATIME_NOW
+     * are both set, then set it to the current time.  Else, set it to the
+     * time specified in the request.  Same goes for mtime.  Use utimensat(2)
+     * as it allows ATIME and MTIME to be changed independently, and has
+     * nanosecond resolution which fuse also has.
+     */
+    if (req->valid & (FATTR_ATIME | FATTR_MTIME)) {
+        times[0].tv_nsec = UTIME_OMIT;
+        times[1].tv_nsec = UTIME_OMIT;
+        if (req->valid & FATTR_ATIME) {
+            if (req->valid & FATTR_ATIME_NOW) {
+              times[0].tv_nsec = UTIME_NOW;
+            } else {
+              times[0].tv_sec = req->atime;
+              times[0].tv_nsec = req->atimensec;
+            }
+        }
+        if (req->valid & FATTR_MTIME) {
+            if (req->valid & FATTR_MTIME_NOW) {
+              times[1].tv_nsec = UTIME_NOW;
+            } else {
+              times[1].tv_sec = req->mtime;
+              times[1].tv_nsec = req->mtimensec;
+            }
+        }
+        TRACE("[%d] Calling utimensat on %s with atime %ld, mtime=%ld\n",
+                handler->token, path, times[0].tv_sec, times[1].tv_sec);
+        if (utimensat(-1, path, times, 0) < 0) {
+            return -errno;
+        }
+    }
+    return fuse_reply_attr(fuse, hdr->unique, hdr->nodeid, path);
+}
+
+static int handle_mknod(struct fuse* fuse, struct fuse_handler* handler,
+        const struct fuse_in_header* hdr, const struct fuse_mknod_in* req, const char* name)
+{
+    struct node* parent_node;
+    char parent_path[PATH_MAX];
+    char child_path[PATH_MAX];
+    const char* actual_name;
+
+    pthread_mutex_lock(&fuse->lock);
+    parent_node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid,
+            parent_path, sizeof(parent_path));
+    TRACE("[%d] MKNOD %s 0%o @ %llx (%s)\n", handler->token,
+            name, req->mode, hdr->nodeid, parent_node ? parent_node->name : "?");
+    pthread_mutex_unlock(&fuse->lock);
+
+    if (!parent_node || !(actual_name = find_file_within(parent_path, name,
+            child_path, sizeof(child_path), 1))) {
+        return -ENOENT;
+    }
+    __u32 mode = (req->mode & (~0777)) | 0664;
+    if (mknod(child_path, mode, req->rdev) < 0) {
+        return -errno;
+    }
+    return fuse_reply_entry(fuse, hdr->unique, parent_node, name, actual_name, child_path);
+}
+
+static int handle_mkdir(struct fuse* fuse, struct fuse_handler* handler,
+        const struct fuse_in_header* hdr, const struct fuse_mkdir_in* req, const char* name)
+{
+    struct node* parent_node;
+    char parent_path[PATH_MAX];
+    char child_path[PATH_MAX];
+    const char* actual_name;
+
+    pthread_mutex_lock(&fuse->lock);
+    parent_node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid,
+            parent_path, sizeof(parent_path));
+    TRACE("[%d] MKDIR %s 0%o @ %llx (%s)\n", handler->token,
+            name, req->mode, hdr->nodeid, parent_node ? parent_node->name : "?");
+    pthread_mutex_unlock(&fuse->lock);
+
+    if (!parent_node || !(actual_name = find_file_within(parent_path, name,
+            child_path, sizeof(child_path), 1))) {
+        return -ENOENT;
+    }
+    __u32 mode = (req->mode & (~0777)) | 0775;
+    if (mkdir(child_path, mode) < 0) {
+        return -errno;
+    }
+    return fuse_reply_entry(fuse, hdr->unique, parent_node, name, actual_name, child_path);
+}
+
+static int handle_unlink(struct fuse* fuse, struct fuse_handler* handler,
+        const struct fuse_in_header* hdr, const char* name)
+{
+    struct node* parent_node;
+    char parent_path[PATH_MAX];
+    char child_path[PATH_MAX];
+
+    pthread_mutex_lock(&fuse->lock);
+    parent_node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid,
+            parent_path, sizeof(parent_path));
+    TRACE("[%d] UNLINK %s @ %llx (%s)\n", handler->token,
+            name, hdr->nodeid, parent_node ? parent_node->name : "?");
+    pthread_mutex_unlock(&fuse->lock);
+
+    if (!parent_node || !find_file_within(parent_path, name,
+            child_path, sizeof(child_path), 1)) {
+        return -ENOENT;
+    }
+    if (unlink(child_path) < 0) {
+        return -errno;
+    }
+    return 0;
+}
+
+static int handle_rmdir(struct fuse* fuse, struct fuse_handler* handler,
+        const struct fuse_in_header* hdr, const char* name)
+{
+    struct node* parent_node;
+    char parent_path[PATH_MAX];
+    char child_path[PATH_MAX];
+
+    pthread_mutex_lock(&fuse->lock);
+    parent_node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid,
+            parent_path, sizeof(parent_path));
+    TRACE("[%d] RMDIR %s @ %llx (%s)\n", handler->token,
+            name, hdr->nodeid, parent_node ? parent_node->name : "?");
+    pthread_mutex_unlock(&fuse->lock);
+
+    if (!parent_node || !find_file_within(parent_path, name,
+            child_path, sizeof(child_path), 1)) {
+        return -ENOENT;
+    }
+    if (rmdir(child_path) < 0) {
+        return -errno;
+    }
+    return 0;
+}
+
+static int handle_rename(struct fuse* fuse, struct fuse_handler* handler,
+        const struct fuse_in_header* hdr, const struct fuse_rename_in* req,
+        const char* old_name, const char* new_name)
+{
+    struct node* old_parent_node;
+    struct node* new_parent_node;
+    struct node* child_node;
+    char old_parent_path[PATH_MAX];
+    char new_parent_path[PATH_MAX];
+    char old_child_path[PATH_MAX];
+    char new_child_path[PATH_MAX];
+    const char* new_actual_name;
+    int res;
+
+    pthread_mutex_lock(&fuse->lock);
+    old_parent_node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid,
+            old_parent_path, sizeof(old_parent_path));
+    new_parent_node = lookup_node_and_path_by_id_locked(fuse, req->newdir,
+            new_parent_path, sizeof(new_parent_path));
+    TRACE("[%d] RENAME %s->%s @ %llx (%s) -> %llx (%s)\n", handler->token,
+            old_name, new_name,
+            hdr->nodeid, old_parent_node ? old_parent_node->name : "?",
+            req->newdir, new_parent_node ? new_parent_node->name : "?");
+    if (!old_parent_node || !new_parent_node) {
+        res = -ENOENT;
+        goto lookup_error;
+    }
+    child_node = lookup_child_by_name_locked(old_parent_node, old_name);
+    if (!child_node || get_node_path_locked(child_node,
+            old_child_path, sizeof(old_child_path)) < 0) {
+        res = -ENOENT;
+        goto lookup_error;
+    }
+    acquire_node_locked(child_node);
+    pthread_mutex_unlock(&fuse->lock);
+
+    /* Special case for renaming a file where destination is same path
+     * differing only by case.  In this case we don't want to look for a case
+     * insensitive match.  This allows commands like "mv foo FOO" to work as expected.
+     */
+    int search = old_parent_node != new_parent_node
+            || strcasecmp(old_name, new_name);
+    if (!(new_actual_name = find_file_within(new_parent_path, new_name,
+            new_child_path, sizeof(new_child_path), search))) {
+        res = -ENOENT;
+        goto io_error;
+    }
+
+    TRACE("[%d] RENAME %s->%s\n", handler->token, old_child_path, new_child_path);
+    res = rename(old_child_path, new_child_path);
+    if (res < 0) {
+        res = -errno;
+        goto io_error;
+    }
+
+    pthread_mutex_lock(&fuse->lock);
+    res = rename_node_locked(child_node, new_name, new_actual_name);
+    if (!res) {
+        remove_node_from_parent_locked(child_node);
+        add_node_to_parent_locked(child_node, new_parent_node);
+    }
+    goto done;
+
+io_error:
+    pthread_mutex_lock(&fuse->lock);
+done:
+    release_node_locked(child_node);
+lookup_error:
+    pthread_mutex_unlock(&fuse->lock);
+    return res;
+}
+
+static int handle_open(struct fuse* fuse, struct fuse_handler* handler,
+        const struct fuse_in_header* hdr, const struct fuse_open_in* req)
+{
+    struct node* node;
+    char path[PATH_MAX];
+    struct fuse_open_out out;
+    struct handle *h;
+
+    pthread_mutex_lock(&fuse->lock);
+    node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid, path, sizeof(path));
+    TRACE("[%d] OPEN 0%o @ %llx (%s)\n", handler->token,
+            req->flags, hdr->nodeid, node ? node->name : "?");
+    pthread_mutex_unlock(&fuse->lock);
+
+    if (!node) {
+        return -ENOENT;
+    }
+    h = malloc(sizeof(*h));
+    if (!h) {
+        return -ENOMEM;
+    }
+    TRACE("[%d] OPEN %s\n", handler->token, path);
+    h->fd = open(path, req->flags);
+    if (h->fd < 0) {
+        free(h);
+        return -errno;
+    }
+    out.fh = ptr_to_id(h);
+    out.open_flags = 0;
+    out.padding = 0;
+    fuse_reply(fuse, hdr->unique, &out, sizeof(out));
+    return NO_STATUS;
+}
+
+static int handle_read(struct fuse* fuse, struct fuse_handler* handler,
+        const struct fuse_in_header* hdr, const struct fuse_read_in* req)
+{
+    struct handle *h = id_to_ptr(req->fh);
+    __u64 unique = hdr->unique;
+    __u32 size = req->size;
+    __u64 offset = req->offset;
+    int res;
+
+    /* Don't access any other fields of hdr or req beyond this point, the read buffer
+     * overlaps the request buffer and will clobber data in the request.  This
+     * saves us 128KB per request handler thread at the cost of this scary comment. */
+
+    TRACE("[%d] READ %p(%d) %u@%llu\n", handler->token,
+            h, h->fd, size, offset);
+    if (size > sizeof(handler->read_buffer)) {
+        return -EINVAL;
+    }
+    res = pread64(h->fd, handler->read_buffer, size, offset);
+    if (res < 0) {
+        return -errno;
+    }
+    fuse_reply(fuse, unique, handler->read_buffer, res);
+    return NO_STATUS;
+}
+
+static int handle_write(struct fuse* fuse, struct fuse_handler* handler,
+        const struct fuse_in_header* hdr, const struct fuse_write_in* req,
+        const void* buffer)
+{
+    struct fuse_write_out out;
+    struct handle *h = id_to_ptr(req->fh);
+    int res;
+
+    TRACE("[%d] WRITE %p(%d) %u@%llu\n", handler->token,
+            h, h->fd, req->size, req->offset);
+    res = pwrite64(h->fd, buffer, req->size, req->offset);
+    if (res < 0) {
+        return -errno;
+    }
+    out.size = res;
+    fuse_reply(fuse, hdr->unique, &out, sizeof(out));
+    return NO_STATUS;
+}
+
+static int handle_statfs(struct fuse* fuse, struct fuse_handler* handler,
+        const struct fuse_in_header* hdr)
+{
+    char path[PATH_MAX];
+    struct statfs stat;
+    struct fuse_statfs_out out;
+    int res;
+
+    pthread_mutex_lock(&fuse->lock);
+    TRACE("[%d] STATFS\n", handler->token);
+    res = get_node_path_locked(&fuse->root, path, sizeof(path));
+    pthread_mutex_unlock(&fuse->lock);
+    if (res < 0) {
+        return -ENOENT;
+    }
+    if (statfs(fuse->root.name, &stat) < 0) {
+        return -errno;
+    }
+    memset(&out, 0, sizeof(out));
+    out.st.blocks = stat.f_blocks;
+    out.st.bfree = stat.f_bfree;
+    out.st.bavail = stat.f_bavail;
+    out.st.files = stat.f_files;
+    out.st.ffree = stat.f_ffree;
+    out.st.bsize = stat.f_bsize;
+    out.st.namelen = stat.f_namelen;
+    out.st.frsize = stat.f_frsize;
+    fuse_reply(fuse, hdr->unique, &out, sizeof(out));
+    return NO_STATUS;
+}
+
+static int handle_release(struct fuse* fuse, struct fuse_handler* handler,
+        const struct fuse_in_header* hdr, const struct fuse_release_in* req)
+{
+    struct handle *h = id_to_ptr(req->fh);
+
+    TRACE("[%d] RELEASE %p(%d)\n", handler->token, h, h->fd);
+    close(h->fd);
+    free(h);
+    return 0;
+}
+
+static int handle_fsync(struct fuse* fuse, struct fuse_handler* handler,
+        const struct fuse_in_header* hdr, const struct fuse_fsync_in* req)
+{
+    int is_data_sync = req->fsync_flags & 1;
+    struct handle *h = id_to_ptr(req->fh);
+    int res;
+
+    TRACE("[%d] FSYNC %p(%d) is_data_sync=%d\n", handler->token,
+            h, h->fd, is_data_sync);
+    res = is_data_sync ? fdatasync(h->fd) : fsync(h->fd);
+    if (res < 0) {
+        return -errno;
+    }
+    return 0;
+}
+
+static int handle_flush(struct fuse* fuse, struct fuse_handler* handler,
+        const struct fuse_in_header* hdr)
+{
+    TRACE("[%d] FLUSH\n", handler->token);
+    return 0;
+}
+
+static int handle_opendir(struct fuse* fuse, struct fuse_handler* handler,
+        const struct fuse_in_header* hdr, const struct fuse_open_in* req)
+{
+    struct node* node;
+    char path[PATH_MAX];
+    struct fuse_open_out out;
+    struct dirhandle *h;
+
+    pthread_mutex_lock(&fuse->lock);
+    node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid, path, sizeof(path));
+    TRACE("[%d] OPENDIR @ %llx (%s)\n", handler->token,
+            hdr->nodeid, node ? node->name : "?");
+    pthread_mutex_unlock(&fuse->lock);
+
+    if (!node) {
+        return -ENOENT;
+    }
+    h = malloc(sizeof(*h));
+    if (!h) {
+        return -ENOMEM;
+    }
+    TRACE("[%d] OPENDIR %s\n", handler->token, path);
+    h->d = opendir(path);
+    if (!h->d) {
+        free(h);
+        return -errno;
+    }
+    out.fh = ptr_to_id(h);
+    fuse_reply(fuse, hdr->unique, &out, sizeof(out));
+    return NO_STATUS;
+}
+
+static int handle_readdir(struct fuse* fuse, struct fuse_handler* handler,
+        const struct fuse_in_header* hdr, const struct fuse_read_in* req)
+{
+    char buffer[8192];
+    struct fuse_dirent *fde = (struct fuse_dirent*) buffer;
+    struct dirent *de;
+    struct dirhandle *h = id_to_ptr(req->fh);
+
+    TRACE("[%d] READDIR %p\n", handler->token, h);
+    if (req->offset == 0) {
+        /* rewinddir() might have been called above us, so rewind here too */
+        TRACE("[%d] calling rewinddir()\n", handler->token);
+        rewinddir(h->d);
+    }
+    de = readdir(h->d);
+    if (!de) {
+        return 0;
+    }
+    fde->ino = FUSE_UNKNOWN_INO;
+    /* increment the offset so we can detect when rewinddir() seeks back to the beginning */
+    fde->off = req->offset + 1;
+    fde->type = de->d_type;
+    fde->namelen = strlen(de->d_name);
+    memcpy(fde->name, de->d_name, fde->namelen + 1);
+    fuse_reply(fuse, hdr->unique, fde,
+            FUSE_DIRENT_ALIGN(sizeof(struct fuse_dirent) + fde->namelen));
+    return NO_STATUS;
+}
+
+static int handle_releasedir(struct fuse* fuse, struct fuse_handler* handler,
+        const struct fuse_in_header* hdr, const struct fuse_release_in* req)
+{
+    struct dirhandle *h = id_to_ptr(req->fh);
+
+    TRACE("[%d] RELEASEDIR %p\n", handler->token, h);
+    closedir(h->d);
+    free(h);
+    return 0;
+}
+
+static int handle_init(struct fuse* fuse, struct fuse_handler* handler,
+        const struct fuse_in_header* hdr, const struct fuse_init_in* req)
+{
+    struct fuse_init_out out;
+
+    TRACE("[%d] INIT ver=%d.%d maxread=%d flags=%x\n",
+            handler->token, req->major, req->minor, req->max_readahead, req->flags);
+    out.major = FUSE_KERNEL_VERSION;
+    out.minor = FUSE_KERNEL_MINOR_VERSION;
+    out.max_readahead = req->max_readahead;
+    out.flags = FUSE_ATOMIC_O_TRUNC | FUSE_BIG_WRITES;
+    out.max_background = 32;
+    out.congestion_threshold = 32;
+    out.max_write = MAX_WRITE;
+    fuse_reply(fuse, hdr->unique, &out, sizeof(out));
+    return NO_STATUS;
+}
+
+static int handle_fuse_request(struct fuse *fuse, struct fuse_handler* handler,
+        const struct fuse_in_header *hdr, const void *data, size_t data_len)
+{
     switch (hdr->opcode) {
     case FUSE_LOOKUP: { /* bytez[] -> entry_out */
-        TRACE("LOOKUP %llx %s\n", hdr->nodeid, (char*) data);
-        lookup_entry(fuse, node, (char*) data, hdr->unique);
-        return;
+        const char* name = data;
+        return handle_lookup(fuse, handler, hdr, name);
     }
+
     case FUSE_FORGET: {
-        struct fuse_forget_in *req = data;
-        TRACE("FORGET %llx (%s) #%lld\n", hdr->nodeid, node->name, req->nlookup);
-            /* no reply */
-        while (req->nlookup--)
-            node_release(node);
-        return;
+        const struct fuse_forget_in *req = data;
+        return handle_forget(fuse, handler, hdr, req);
     }
+
     case FUSE_GETATTR: { /* getattr_in -> attr_out */
-        struct fuse_getattr_in *req = data;
-        struct fuse_attr_out out;
-
-        TRACE("GETATTR flags=%x fh=%llx\n", req->getattr_flags, req->fh);
-
-        memset(&out, 0, sizeof(out));
-        node_get_attr(node, &out.attr);
-        out.attr_valid = 10;
-
-        fuse_reply(fuse, hdr->unique, &out, sizeof(out));
-        return;
+        const struct fuse_getattr_in *req = data;
+        return handle_getattr(fuse, handler, hdr, req);
     }
+
     case FUSE_SETATTR: { /* setattr_in -> attr_out */
-        struct fuse_setattr_in *req = data;
-        struct fuse_attr_out out;
-        char *path, buffer[PATH_BUFFER_SIZE];
-        int res = 0;
-        struct timespec times[2];
-
-        TRACE("SETATTR fh=%llx id=%llx valid=%x\n",
-              req->fh, hdr->nodeid, req->valid);
-
-        /* XXX: incomplete implementation on purpose.   chmod/chown
-         * should NEVER be implemented.*/
-
-        path = node_get_path(node, buffer, 0);
-        if (req->valid & FATTR_SIZE)
-            res = truncate(path, req->size);
-        if (res)
-            goto getout;
-
-        /* Handle changing atime and mtime.  If FATTR_ATIME_and FATTR_ATIME_NOW
-         * are both set, then set it to the current time.  Else, set it to the
-         * time specified in the request.  Same goes for mtime.  Use utimensat(2)
-         * as it allows ATIME and MTIME to be changed independently, and has
-         * nanosecond resolution which fuse also has.
-         */
-        if (req->valid & (FATTR_ATIME | FATTR_MTIME)) {
-            times[0].tv_nsec = UTIME_OMIT;
-            times[1].tv_nsec = UTIME_OMIT;
-            if (req->valid & FATTR_ATIME) {
-                if (req->valid & FATTR_ATIME_NOW) {
-                  times[0].tv_nsec = UTIME_NOW;
-                } else {
-                  times[0].tv_sec = req->atime;
-                  times[0].tv_nsec = req->atimensec;
-                }
-            }
-            if (req->valid & FATTR_MTIME) {
-                if (req->valid & FATTR_MTIME_NOW) {
-                  times[1].tv_nsec = UTIME_NOW;
-                } else {
-                  times[1].tv_sec = req->mtime;
-                  times[1].tv_nsec = req->mtimensec;
-                }
-            }
-            TRACE("Calling utimensat on %s with atime %ld, mtime=%ld\n", path, times[0].tv_sec, times[1].tv_sec);
-            res = utimensat(-1, path, times, 0);
-        }
-
-        getout:
-        memset(&out, 0, sizeof(out));
-        node_get_attr(node, &out.attr);
-        out.attr_valid = 10;
-
-        if (res)
-            fuse_status(fuse, hdr->unique, -errno);
-        else
-            fuse_reply(fuse, hdr->unique, &out, sizeof(out));
-        return;
+        const struct fuse_setattr_in *req = data;
+        return handle_setattr(fuse, handler, hdr, req);
     }
+
 //    case FUSE_READLINK:
 //    case FUSE_SYMLINK:
     case FUSE_MKNOD: { /* mknod_in, bytez[] -> entry_out */
-        struct fuse_mknod_in *req = data;
-        char *path, buffer[PATH_BUFFER_SIZE];
-        char *name = ((char*) data) + sizeof(*req);
-        int res;
-
-        TRACE("MKNOD %s @ %llx\n", name, hdr->nodeid);
-        path = node_get_path(node, buffer, name);
-
-        req->mode = (req->mode & (~0777)) | 0664;
-        res = mknod(path, req->mode, req->rdev); /* XXX perm?*/
-        if (res < 0) {
-            fuse_status(fuse, hdr->unique, -errno);
-        } else {
-            lookup_entry(fuse, node, name, hdr->unique);
-        }
-        return;
+        const struct fuse_mknod_in *req = data;
+        const char *name = ((const char*) data) + sizeof(*req);
+        return handle_mknod(fuse, handler, hdr, req, name);
     }
+
     case FUSE_MKDIR: { /* mkdir_in, bytez[] -> entry_out */
-        struct fuse_mkdir_in *req = data;
-        struct fuse_entry_out out;
-        char *path, buffer[PATH_BUFFER_SIZE];
-        char *name = ((char*) data) + sizeof(*req);
-        int res;
-
-        TRACE("MKDIR %s @ %llx 0%o\n", name, hdr->nodeid, req->mode);
-        path = node_get_path(node, buffer, name);
-
-        req->mode = (req->mode & (~0777)) | 0775;
-        res = mkdir(path, req->mode);
-        if (res < 0) {
-            fuse_status(fuse, hdr->unique, -errno);
-        } else {
-            lookup_entry(fuse, node, name, hdr->unique);
-        }
-        return;
+        const struct fuse_mkdir_in *req = data;
+        const char *name = ((const char*) data) + sizeof(*req);
+        return handle_mkdir(fuse, handler, hdr, req, name);
     }
+
     case FUSE_UNLINK: { /* bytez[] -> */
-        char *path, buffer[PATH_BUFFER_SIZE];
-        int res;
-        TRACE("UNLINK %s @ %llx\n", (char*) data, hdr->nodeid);
-        path = node_get_path(node, buffer, (char*) data);
-        res = unlink(path);
-        fuse_status(fuse, hdr->unique, res ? -errno : 0);
-        return;
+        const char* name = data;
+        return handle_unlink(fuse, handler, hdr, name);
     }
+
     case FUSE_RMDIR: { /* bytez[] -> */
-        char *path, buffer[PATH_BUFFER_SIZE];
-        int res;
-        TRACE("RMDIR %s @ %llx\n", (char*) data, hdr->nodeid);
-        path = node_get_path(node, buffer, (char*) data);
-        res = rmdir(path);
-        fuse_status(fuse, hdr->unique, res ? -errno : 0);
-        return;
+        const char* name = data;
+        return handle_rmdir(fuse, handler, hdr, name);
     }
+
     case FUSE_RENAME: { /* rename_in, oldname, newname ->  */
-        struct fuse_rename_in *req = data;
-        char *oldname = ((char*) data) + sizeof(*req);
-        char *newname = oldname + strlen(oldname) + 1;
-        char *oldpath, oldbuffer[PATH_BUFFER_SIZE];
-        char *newpath, newbuffer[PATH_BUFFER_SIZE];
-        struct node *target;
-        struct node *newparent;
-        int res;
-
-        TRACE("RENAME %s->%s @ %llx\n", oldname, newname, hdr->nodeid);
-
-        target = lookup_child_by_name(node, oldname);
-        if (!target) {
-            fuse_status(fuse, hdr->unique, -ENOENT);
-            return;
-        }
-        oldpath = node_get_path(node, oldbuffer, oldname);
-
-        newparent = lookup_by_inode(fuse, req->newdir);
-        if (!newparent) {
-            fuse_status(fuse, hdr->unique, -ENOENT);
-            return;
-        }
-        if (newparent == node) {
-            /* Special case for renaming a file where destination
-             * is same path differing only by case.
-             * In this case we don't want to look for a case insensitive match.
-             * This allows commands like "mv foo FOO" to work as expected.
-             */
-            newpath = do_node_get_path(newparent, newbuffer, newname, NO_CASE_SENSITIVE_MATCH);
-        } else {
-            newpath = node_get_path(newparent, newbuffer, newname);
-        }
-
-        if (!remove_child(node, target->nid)) {
-            ERROR("RENAME remove_child not found");
-            fuse_status(fuse, hdr->unique, -ENOENT);
-            return;
-        }
-        if (!rename_node(target, newname)) {
-            fuse_status(fuse, hdr->unique, -ENOMEM);
-            return;
-        }
-        add_node_to_parent(target, newparent);
-
-        res = rename(oldpath, newpath);
-        TRACE("RENAME result %d\n", res);
-
-        fuse_status(fuse, hdr->unique, res ? -errno : 0);
-        return;
+        const struct fuse_rename_in *req = data;
+        const char *old_name = ((const char*) data) + sizeof(*req);
+        const char *new_name = old_name + strlen(old_name) + 1;
+        return handle_rename(fuse, handler, hdr, req, old_name, new_name);
     }
-//    case FUSE_LINK:        
+
+//    case FUSE_LINK:
     case FUSE_OPEN: { /* open_in -> open_out */
-        struct fuse_open_in *req = data;
-        struct fuse_open_out out;
-        char *path, buffer[PATH_BUFFER_SIZE];
-        struct handle *h;
-
-        h = malloc(sizeof(*h));
-        if (!h) {
-            fuse_status(fuse, hdr->unique, -ENOMEM);
-            return;
-        }
-
-        path = node_get_path(node, buffer, 0);
-        TRACE("OPEN %llx '%s' 0%o fh=%p\n", hdr->nodeid, path, req->flags, h);
-        h->fd = open(path, req->flags);
-        if (h->fd < 0) {
-            ERROR("ERROR\n");
-            fuse_status(fuse, hdr->unique, -errno);
-            free(h);
-            return;
-        }
-        out.fh = ptr_to_id(h);
-        out.open_flags = 0;
-        out.padding = 0;
-        fuse_reply(fuse, hdr->unique, &out, sizeof(out));
-        return;
+        const struct fuse_open_in *req = data;
+        return handle_open(fuse, handler, hdr, req);
     }
+
     case FUSE_READ: { /* read_in -> byte[] */
-        char buffer[128 * 1024];
-        struct fuse_read_in *req = data;
-        struct handle *h = id_to_ptr(req->fh);
-        int res;
-        TRACE("READ %p(%d) %u@%llu\n", h, h->fd, req->size, req->offset);
-        if (req->size > sizeof(buffer)) {
-            fuse_status(fuse, hdr->unique, -EINVAL);
-            return;
-        }
-        res = pread64(h->fd, buffer, req->size, req->offset);
-        if (res < 0) {
-            fuse_status(fuse, hdr->unique, -errno);
-            return;
-        }
-        fuse_reply(fuse, hdr->unique, buffer, res);
-        return;
+        const struct fuse_read_in *req = data;
+        return handle_read(fuse, handler, hdr, req);
     }
+
     case FUSE_WRITE: { /* write_in, byte[write_in.size] -> write_out */
-        struct fuse_write_in *req = data;
-        struct fuse_write_out out;
-        struct handle *h = id_to_ptr(req->fh);
-        int res;
-        TRACE("WRITE %p(%d) %u@%llu\n", h, h->fd, req->size, req->offset);
-        res = pwrite64(h->fd, ((char*) data) + sizeof(*req), req->size, req->offset);
-        if (res < 0) {
-            fuse_status(fuse, hdr->unique, -errno);
-            return;
-        }
-        out.size = res;
-        fuse_reply(fuse, hdr->unique, &out, sizeof(out));
-        goto oops;
+        const struct fuse_write_in *req = data;
+        const void* buffer = (const __u8*)data + sizeof(*req);
+        return handle_write(fuse, handler, hdr, req, buffer);
     }
+
     case FUSE_STATFS: { /* getattr_in -> attr_out */
-        struct statfs stat;
-        struct fuse_statfs_out out;
-        int res;
-
-        TRACE("STATFS\n");
-
-        if (statfs(fuse->root.name, &stat)) {
-            fuse_status(fuse, hdr->unique, -errno);
-            return;
-        }
-
-        memset(&out, 0, sizeof(out));
-        out.st.blocks = stat.f_blocks;
-        out.st.bfree = stat.f_bfree;
-        out.st.bavail = stat.f_bavail;
-        out.st.files = stat.f_files;
-        out.st.ffree = stat.f_ffree;
-        out.st.bsize = stat.f_bsize;
-        out.st.namelen = stat.f_namelen;
-        out.st.frsize = stat.f_frsize;
-        fuse_reply(fuse, hdr->unique, &out, sizeof(out));
-        return;
+        return handle_statfs(fuse, handler, hdr);
     }
+
     case FUSE_RELEASE: { /* release_in -> */
-        struct fuse_release_in *req = data;
-        struct handle *h = id_to_ptr(req->fh);
-        TRACE("RELEASE %p(%d)\n", h, h->fd);
-        close(h->fd);
-        free(h);
-        fuse_status(fuse, hdr->unique, 0);
-        return;
+        const struct fuse_release_in *req = data;
+        return handle_release(fuse, handler, hdr, req);
     }
-//    case FUSE_FSYNC:
+
+    case FUSE_FSYNC: {
+        const struct fuse_fsync_in *req = data;
+        return handle_fsync(fuse, handler, hdr, req);
+    }
+
 //    case FUSE_SETXATTR:
 //    case FUSE_GETXATTR:
 //    case FUSE_LISTXATTR:
 //    case FUSE_REMOVEXATTR:
-    case FUSE_FLUSH:
-        fuse_status(fuse, hdr->unique, 0);
-        return;
+    case FUSE_FLUSH: {
+        return handle_flush(fuse, handler, hdr);
+    }
+
     case FUSE_OPENDIR: { /* open_in -> open_out */
-        struct fuse_open_in *req = data;
-        struct fuse_open_out out;
-        char *path, buffer[PATH_BUFFER_SIZE];
-        struct dirhandle *h;
-
-        h = malloc(sizeof(*h));
-        if (!h) {
-            fuse_status(fuse, hdr->unique, -ENOMEM);
-            return;
-        }
-
-        path = node_get_path(node, buffer, 0);
-        TRACE("OPENDIR %llx '%s'\n", hdr->nodeid, path);
-        h->d = opendir(path);
-        if (h->d == 0) {
-            ERROR("ERROR\n");
-            fuse_status(fuse, hdr->unique, -errno);
-            free(h);
-            return;
-        }
-        out.fh = ptr_to_id(h);
-        fuse_reply(fuse, hdr->unique, &out, sizeof(out));
-        return;
+        const struct fuse_open_in *req = data;
+        return handle_opendir(fuse, handler, hdr, req);
     }
+
     case FUSE_READDIR: {
-        struct fuse_read_in *req = data;
-        char buffer[8192];
-        struct fuse_dirent *fde = (struct fuse_dirent*) buffer;
-        struct dirent *de;
-        struct dirhandle *h = id_to_ptr(req->fh);
-        TRACE("READDIR %p\n", h);
-        if (req->offset == 0) {
-            /* rewinddir() might have been called above us, so rewind here too */
-            TRACE("calling rewinddir()\n");
-            rewinddir(h->d);
-        }
-        de = readdir(h->d);
-        if (!de) {
-            fuse_status(fuse, hdr->unique, 0);
-            return;
-        }
-        fde->ino = FUSE_UNKNOWN_INO;
-        /* increment the offset so we can detect when rewinddir() seeks back to the beginning */
-        fde->off = req->offset + 1;
-        fde->type = de->d_type;
-        fde->namelen = strlen(de->d_name);
-        memcpy(fde->name, de->d_name, fde->namelen + 1);
-        fuse_reply(fuse, hdr->unique, fde,
-                   FUSE_DIRENT_ALIGN(sizeof(struct fuse_dirent) + fde->namelen));
-        return;
+        const struct fuse_read_in *req = data;
+        return handle_readdir(fuse, handler, hdr, req);
     }
+
     case FUSE_RELEASEDIR: { /* release_in -> */
-        struct fuse_release_in *req = data;
-        struct dirhandle *h = id_to_ptr(req->fh);
-        TRACE("RELEASEDIR %p\n",h);
-        closedir(h->d);
-        free(h);
-        fuse_status(fuse, hdr->unique, 0);
-        return;
+        const struct fuse_release_in *req = data;
+        return handle_releasedir(fuse, handler, hdr, req);
     }
+
 //    case FUSE_FSYNCDIR:
     case FUSE_INIT: { /* init_in -> init_out */
-        struct fuse_init_in *req = data;
-        struct fuse_init_out out;
-        
-        TRACE("INIT ver=%d.%d maxread=%d flags=%x\n",
-                req->major, req->minor, req->max_readahead, req->flags);
-
-        out.major = FUSE_KERNEL_VERSION;
-        out.minor = FUSE_KERNEL_MINOR_VERSION;
-        out.max_readahead = req->max_readahead;
-        out.flags = FUSE_ATOMIC_O_TRUNC | FUSE_BIG_WRITES;
-        out.max_background = 32;
-        out.congestion_threshold = 32;
-        out.max_write = 256 * 1024;
-
-        fuse_reply(fuse, hdr->unique, &out, sizeof(out));
-        return;
+        const struct fuse_init_in *req = data;
+        return handle_init(fuse, handler, hdr, req);
     }
+
     default: {
-        struct fuse_out_header h;
-        ERROR("NOTIMPL op=%d uniq=%llx nid=%llx\n",
-                hdr->opcode, hdr->unique, hdr->nodeid);
-
-        oops:
-        h.len = sizeof(h);
-        h.error = -ENOSYS;
-        h.unique = hdr->unique;
-        write(fuse->fd, &h, sizeof(h));
-        break;
+        TRACE("[%d] NOTIMPL op=%d uniq=%llx nid=%llx\n",
+                handler->token, hdr->opcode, hdr->unique, hdr->nodeid);
+        return -ENOSYS;
     }
-    }   
+    }
 }
 
-void handle_fuse_requests(struct fuse *fuse)
+static void handle_fuse_requests(struct fuse_handler* handler)
 {
-    unsigned char req[256 * 1024 + 128];
-    int len;
-    
+    struct fuse* fuse = handler->fuse;
     for (;;) {
-        len = read(fuse->fd, req, sizeof(req));
+        ssize_t len = read(fuse->fd,
+                handler->request_buffer, sizeof(handler->request_buffer));
         if (len < 0) {
-            if (errno == EINTR)
-                continue;
-            ERROR("handle_fuse_requests: errno=%d\n", errno);
-            return;
+            if (errno != EINTR) {
+                ERROR("[%d] handle_fuse_requests: errno=%d\n", handler->token, errno);
+            }
+            continue;
         }
-        handle_fuse_request(fuse, (void*) req, (void*) (req + sizeof(struct fuse_in_header)), len);
+
+        if ((size_t)len < sizeof(struct fuse_in_header)) {
+            ERROR("[%d] request too short: len=%zu\n", handler->token, (size_t)len);
+            continue;
+        }
+
+        const struct fuse_in_header *hdr = (void*)handler->request_buffer;
+        if (hdr->len != (size_t)len) {
+            ERROR("[%d] malformed header: len=%zu, hdr->len=%u\n",
+                    handler->token, (size_t)len, hdr->len);
+            continue;
+        }
+
+        const void *data = handler->request_buffer + sizeof(struct fuse_in_header);
+        size_t data_len = len - sizeof(struct fuse_in_header);
+        __u64 unique = hdr->unique;
+        int res = handle_fuse_request(fuse, handler, hdr, data, data_len);
+
+        /* We do not access the request again after this point because the underlying
+         * buffer storage may have been reused while processing the request. */
+
+        if (res != NO_STATUS) {
+            if (res) {
+                TRACE("[%d] ERROR %d\n", handler->token, res);
+            }
+            fuse_status(fuse, unique, res);
+        }
     }
 }
 
+static void* start_handler(void* data)
+{
+    struct fuse_handler* handler = data;
+    handle_fuse_requests(handler);
+    return NULL;
+}
+
+static int ignite_fuse(struct fuse* fuse, int num_threads)
+{
+    struct fuse_handler* handlers;
+    int i;
+
+    handlers = malloc(num_threads * sizeof(struct fuse_handler));
+    if (!handlers) {
+        ERROR("cannot allocate storage for threads");
+        return -ENOMEM;
+    }
+
+    for (i = 0; i < num_threads; i++) {
+        handlers[i].fuse = fuse;
+        handlers[i].token = i;
+    }
+
+    for (i = 1; i < num_threads; i++) {
+        pthread_t thread;
+        int res = pthread_create(&thread, NULL, start_handler, &handlers[i]);
+        if (res) {
+            ERROR("failed to start thread #%d, error=%d", i, res);
+            goto quit;
+        }
+    }
+    handle_fuse_requests(&handlers[0]);
+    ERROR("terminated prematurely");
+
+    /* don't bother killing all of the other threads or freeing anything,
+     * should never get here anyhow */
+quit:
+    exit(1);
+}
+
 static int usage()
 {
-    ERROR("usage: sdcard [-l -f] <path> <uid> <gid>\n\n\t-l force file names to lower case when creating new files\n\t-f fix up file system before starting (repairs bad file name case and group ownership)\n");
-    return -1;
+    ERROR("usage: sdcard [-t<threads>] <path> <uid> <gid>\n"
+            "    -t<threads>: specify number of threads to use, default -t%d\n"
+            "\n", DEFAULT_NUM_THREADS);
+    return 1;
+}
+
+static int run(const char* path, uid_t uid, gid_t gid, int num_threads)
+{
+    int fd;
+    char opts[256];
+    int res;
+    struct fuse fuse;
+
+    /* cleanup from previous instance, if necessary */
+    umount2(MOUNT_POINT, 2);
+
+    fd = open("/dev/fuse", O_RDWR);
+    if (fd < 0){
+        ERROR("cannot open fuse device (error %d)\n", errno);
+        return -1;
+    }
+
+    snprintf(opts, sizeof(opts),
+            "fd=%i,rootmode=40000,default_permissions,allow_other,user_id=%d,group_id=%d",
+            fd, uid, gid);
+
+    res = mount("/dev/fuse", MOUNT_POINT, "fuse", MS_NOSUID | MS_NODEV, opts);
+    if (res < 0) {
+        ERROR("cannot mount fuse filesystem (error %d)\n", errno);
+        goto error;
+    }
+
+    res = setgid(gid);
+    if (res < 0) {
+        ERROR("cannot setgid (error %d)\n", errno);
+        goto error;
+    }
+
+    res = setuid(uid);
+    if (res < 0) {
+        ERROR("cannot setuid (error %d)\n", errno);
+        goto error;
+    }
+
+    fuse_init(&fuse, fd, path);
+
+    umask(0);
+    res = ignite_fuse(&fuse, num_threads);
+
+    /* we do not attempt to umount the file system here because we are no longer
+     * running as the root user */
+
+error:
+    close(fd);
+    return res;
 }
 
 int main(int argc, char **argv)
 {
-    struct fuse fuse;
-    char opts[256];
-    int fd;
     int res;
     const char *path = NULL;
+    uid_t uid = 0;
+    gid_t gid = 0;
+    int num_threads = DEFAULT_NUM_THREADS;
     int i;
 
     for (i = 1; i < argc; i++) {
         char* arg = argv[i];
-        if (!path)
+        if (!strncmp(arg, "-t", 2))
+            num_threads = strtoul(arg + 2, 0, 10);
+        else if (!path)
             path = arg;
-        else if (uid == -1)
+        else if (!uid)
             uid = strtoul(arg, 0, 10);
-        else if (gid == -1)
+        else if (!gid)
             gid = strtoul(arg, 0, 10);
         else {
             ERROR("too many arguments\n");
@@ -985,42 +1330,15 @@
         ERROR("no path specified\n");
         return usage();
     }
-    if (uid <= 0 || gid <= 0) {
+    if (!uid || !gid) {
         ERROR("uid and gid must be nonzero\n");
         return usage();
     }
-
-        /* cleanup from previous instance, if necessary */
-    umount2(MOUNT_POINT, 2);
-
-    fd = open("/dev/fuse", O_RDWR);
-    if (fd < 0){
-        ERROR("cannot open fuse device (%d)\n", errno);
-        return -1;
+    if (num_threads < 1) {
+        ERROR("number of threads must be at least 1\n");
+        return usage();
     }
 
-    sprintf(opts, "fd=%i,rootmode=40000,default_permissions,allow_other,"
-            "user_id=%d,group_id=%d", fd, uid, gid);
-    
-    res = mount("/dev/fuse", MOUNT_POINT, "fuse", MS_NOSUID | MS_NODEV, opts);
-    if (res < 0) {
-        ERROR("cannot mount fuse filesystem (%d)\n", errno);
-        return -1;
-    }
-
-    if (setgid(gid) < 0) {
-        ERROR("cannot setgid!\n");
-        return -1;
-    }
-    if (setuid(uid) < 0) {
-        ERROR("cannot setuid!\n");
-        return -1;
-    }
-
-    fuse_init(&fuse, fd, path);
-
-    umask(0);
-    handle_fuse_requests(&fuse);
-    
-    return 0;
+    res = run(path, uid, gid, num_threads);
+    return res < 0 ? 1 : 0;
 }