The Duet framework code:

- bittree.c: red-black bitmap tree that keeps track of items of interest
- debug.c: functions used to print information used to debug Duet
- hash.c: implementation of the global hash table where page events are stored
  for all tasks
- hook.c: the function invoked by the page cache hooks when Duet is online
- init.c: routines used to bring Duet online or offline
- path.c: routines performing resolution of UUIDs to paths using d_path
- task.c: implementation of Duet task fd operations

Signed-off-by: George Amvrosiadis <[email protected]>
---
 init/Kconfig      |   2 +
 mm/Makefile       |   1 +
 mm/duet/Kconfig   |  31 +++
 mm/duet/Makefile  |   7 +
 mm/duet/bittree.c | 537 +++++++++++++++++++++++++++++++++++++++++++++++++
 mm/duet/common.h  | 211 ++++++++++++++++++++
 mm/duet/debug.c   |  98 +++++++++
 mm/duet/hash.c    | 315 +++++++++++++++++++++++++++++
 mm/duet/hook.c    |  81 ++++++++
 mm/duet/init.c    | 172 ++++++++++++++++
 mm/duet/path.c    | 184 +++++++++++++++++
 mm/duet/syscall.h |  61 ++++++
 mm/duet/task.c    | 584 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 13 files changed, 2284 insertions(+)
 create mode 100644 mm/duet/Kconfig
 create mode 100644 mm/duet/Makefile
 create mode 100644 mm/duet/bittree.c
 create mode 100644 mm/duet/common.h
 create mode 100644 mm/duet/debug.c
 create mode 100644 mm/duet/hash.c
 create mode 100644 mm/duet/hook.c
 create mode 100644 mm/duet/init.c
 create mode 100644 mm/duet/path.c
 create mode 100644 mm/duet/syscall.h
 create mode 100644 mm/duet/task.c

diff --git a/init/Kconfig b/init/Kconfig
index c02d897..6f94b5a 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -294,6 +294,8 @@ config USELIB
          earlier, you may need to enable this syscall.  Current systems
          running glibc can safely disable this.
 
+source mm/duet/Kconfig
+
 config AUDIT
        bool "Auditing support"
        depends on NET
diff --git a/mm/Makefile b/mm/Makefile
index 78c6f7d..074c15f 100644
--- a/mm/Makefile
+++ b/mm/Makefile
@@ -99,3 +99,4 @@ obj-$(CONFIG_USERFAULTFD) += userfaultfd.o
 obj-$(CONFIG_IDLE_PAGE_TRACKING) += page_idle.o
 obj-$(CONFIG_FRAME_VECTOR) += frame_vector.o
 obj-$(CONFIG_DEBUG_PAGE_REF) += debug_page_ref.o
+obj-$(CONFIG_DUET) += duet/
diff --git a/mm/duet/Kconfig b/mm/duet/Kconfig
new file mode 100644
index 0000000..2f3a0c5
--- /dev/null
+++ b/mm/duet/Kconfig
@@ -0,0 +1,31 @@
+config DUET
+       bool "Duet framework support"
+
+       help
+         Duet is a framework aiming to reduce the IO footprint of analytics
+         and maintenance work. By exposing page cache events to these tasks,
+         it allows them to adapt their data processing order, in order to
+         benefit from data available in the page cache. Duet's operation is
+         based on hooks into the page cache.
+
+         To compile support for Duet, say Y.
+
+config DUET_STATS
+       bool "Duet statistics collection"
+       depends on DUET
+       help
+         This option enables support for the collection of statistics on the
+         operation of Duet. It will print information about the data structures
+         used internally, and profiling information about the framework.
+
+         If unsure, say N.
+
+config DUET_DEBUG
+       bool "Duet debugging support"
+       depends on DUET
+       help
+         Enable runtime debugging support for the Duet framework. This may
+         enable additional and expensive checks with negative impact on
+         performance.
+
+         To compile debugging support for Duet, say Y. If unsure, say N.
diff --git a/mm/duet/Makefile b/mm/duet/Makefile
new file mode 100644
index 0000000..c0c9e11
--- /dev/null
+++ b/mm/duet/Makefile
@@ -0,0 +1,7 @@
+#
+# Makefile for the linux Duet framework.
+#
+
+obj-$(CONFIG_DUET) += duet.o
+
+duet-y := init.o hash.o hook.o task.o bittree.o path.o debug.o
diff --git a/mm/duet/bittree.c b/mm/duet/bittree.c
new file mode 100644
index 0000000..3b20c35
--- /dev/null
+++ b/mm/duet/bittree.c
@@ -0,0 +1,537 @@
+/*
+ * Copyright (C) 2016 George Amvrosiadis.  All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License v2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include "common.h"
+
+#define BMAP_READ      0x01    /* Read bmaps (overrides other flags) */
+#define BMAP_CHECK     0x02    /* Check given bmap value expression */
+                               /* Sets bmaps to match expression if not set */
+
+/* Bmap expressions can be formed using the following flags: */
+#define BMAP_DONE_SET  0x04    /* Set done bmap values */
+#define BMAP_DONE_RST  0x08    /* Reset done bmap values */
+#define BMAP_RELV_SET  0x10    /* Set relevant bmap values */
+#define BMAP_RELV_RST  0x20    /* Reset relevant bmap values */
+#define BMAP_SEEN_SET  0x40    /* Set seen bmap values */
+#define BMAP_SEEN_RST  0x80    /* Reset seen bmap values */
+
+/* Some macros to make our life easier */
+#define BMAP_ALL_SET   (BMAP_SEEN_SET | BMAP_RELV_SET | BMAP_DONE_SET)
+#define BMAP_ALL_RST   (BMAP_SEEN_RST | BMAP_RELV_RST | BMAP_DONE_RST)
+
+#define BITTREE_RANGE  PAGE_SIZE       /* Bytes per bitmap bit */
+#define BITS_PER_NODE  (32768 * 8)     /* 32KB bitmaps */
+
+#define UUID_IDX(uuid) (((unsigned long long) uuid.gen << 32) | \
+                         (unsigned long long) uuid.ino)
+/*
+ * The following functions are wrappers for the basic bitmap functions.
+ * A bitmap is characterized by a starting offset (bstart). The wrappers
+ * translate an arbitrary idx to the appropriate bit.
+ */
+
+/* Sets (or resets) a single bit */
+static int bmap_set(unsigned long *bmap, __u64 start, __u64 idx, __u8 do_set)
+{
+       __u64 bofft = idx - start;
+
+       if (bofft + 1 >= start + (BITS_PER_NODE * BITTREE_RANGE))
+               return -1;
+
+       /* Convert range to bitmap granularity */
+       do_div(bofft, BITTREE_RANGE);
+
+       if (do_set)
+               bitmap_set(bmap, (unsigned int)bofft, 1);
+       else
+               bitmap_clear(bmap, (unsigned int)bofft, 1);
+
+       return 0;
+}
+
+/* Returns value of bit at idx */
+static int bmap_read(unsigned long *bmap, __u64 start, __u64 idx)
+{
+       __u64 bofft64 = idx - start;
+       unsigned long *p, mask;
+       unsigned int bofft;
+
+       if (bofft64 + 1 >= start + (BITS_PER_NODE * BITTREE_RANGE))
+               return -1;
+
+       /* Convert offset to bitmap granularity */
+       do_div(bofft64, BITTREE_RANGE);
+       bofft = (unsigned int)bofft64;
+
+       /* Check the bits */
+       p = bmap + BIT_WORD(bofft);
+       mask = BITMAP_FIRST_WORD_MASK(bofft) & BITMAP_LAST_WORD_MASK(bofft + 1);
+
+       if ((*p) & mask)
+               return 1;
+
+       return 0;
+}
+
+/* Checks whether a bit is set */
+static int bmap_chk(unsigned long *bmap, __u64 start, __u64 idx, __u8 do_set)
+{
+       __u64 bofft64 = idx - start;
+       unsigned long *p, mask;
+       unsigned int bofft;
+
+       if (bofft64 + 1 >= start + (BITS_PER_NODE * BITTREE_RANGE))
+               return -1;
+
+       /* Convert range to bitmap granularity */
+       do_div(bofft64, BITTREE_RANGE);
+
+       /* Now it is safe to cast these variables */
+       bofft = (unsigned int)bofft64;
+
+       /* Check the bit */
+       p = bmap + BIT_WORD(bofft);
+       mask = BITMAP_FIRST_WORD_MASK(bofft) & BITMAP_LAST_WORD_MASK(bofft + 1);
+
+       if (do_set && !((*p) & mask))
+               return 0;
+       else if (!do_set && !(~(*p) & mask))
+               return 0;
+
+       return 1;
+}
+
+/* Initializes a bitmap tree node */
+static struct bmap_rbnode *bnode_init(struct duet_bittree *bt, __u64 idx)
+{
+       struct bmap_rbnode *bnode = NULL;
+
+#ifdef CONFIG_DUET_STATS
+       if (bt) {
+               (bt->statcur)++;
+               if (bt->statcur > bt->statmax) {
+                       bt->statmax = bt->statcur;
+                       pr_info("duet: %llu BitTree nodes (%llub)\n",
+                               bt->statmax, bt->statmax * BITS_PER_NODE / 8);
+               }
+       }
+#endif /* CONFIG_DUET_STATS */
+
+       bnode = kmalloc(sizeof(*bnode), GFP_NOWAIT);
+       if (!bnode)
+               return NULL;
+
+       bnode->done = kzalloc(sizeof(unsigned long) *
+                       BITS_TO_LONGS(BITS_PER_NODE), GFP_NOWAIT);
+       if (!bnode->done) {
+               kfree(bnode);
+               return NULL;
+       }
+
+       /* Allocate relevant bitmap, if needed */
+       bnode->relv = kzalloc(sizeof(unsigned long) *
+               BITS_TO_LONGS(BITS_PER_NODE), GFP_NOWAIT);
+       if (!bnode->relv) {
+               kfree(bnode->done);
+               kfree(bnode);
+               return NULL;
+       }
+
+       bnode->seen = kzalloc(sizeof(unsigned long) *
+               BITS_TO_LONGS(BITS_PER_NODE), GFP_NOWAIT);
+       if (!bnode->seen) {
+               kfree(bnode->relv);
+               kfree(bnode->done);
+               kfree(bnode);
+               return NULL;
+       }
+
+       RB_CLEAR_NODE(&bnode->node);
+       bnode->idx = idx;
+       return bnode;
+}
+
+static void bnode_dispose(struct bmap_rbnode *bnode, struct rb_node *rbnode,
+       struct duet_bittree *bt)
+{
+#ifdef CONFIG_DUET_STATS
+       if (bt)
+               (bt->statcur)--;
+#endif /* CONFIG_DUET_STATS */
+       rb_erase(rbnode, &bt->root);
+       kfree(bnode->relv);
+       kfree(bnode->seen);
+       kfree(bnode->done);
+       kfree(bnode);
+}
+
+/*
+ * Traverses bitmap nodes to read/set/unset/check a specific bit across 
bitmaps.
+ * May insert/remove bitmap nodes as needed.
+ *
+ * If DUET_BMAP_READ is set:
+ * - the bitmap value for idx are read for one or all bitmaps
+ * Otherwise, if DUET_BMAP_CHECK flag is set:
+ * - return value 1 means the idx matches the given flags
+ * - return value 0 means the idx doesn't match the given flags
+ * Otherwise, if neither flag is set:
+ * - return value 0 means the idx was updated to match given flags
+ *
+ * In all cases, a return value -1 denotes an error.
+ */
+static int __update_tree(struct duet_bittree *bt, __u64 idx, __u8 flags)
+{
+       int found, ret, res;
+       __u64 node_offt, div_rem;
+       struct rb_node **link, *parent;
+       struct bmap_rbnode *bnode = NULL;
+       unsigned long iflags;
+
+       local_irq_save(iflags);
+       spin_lock(&bt->lock);
+
+       div64_u64_rem(idx, BITTREE_RANGE * BITS_PER_NODE, &div_rem);
+       node_offt = idx - div_rem;
+
+       /* Look up BitTree node */
+       found = 0;
+       link = &(bt->root).rb_node;
+       parent = NULL;
+
+       while (*link) {
+               parent = *link;
+               bnode = rb_entry(parent, struct bmap_rbnode, node);
+
+               if (bnode->idx > node_offt) {
+                       link = &(*link)->rb_left;
+               } else if (bnode->idx < node_offt) {
+                       link = &(*link)->rb_right;
+               } else {
+                       found = 1;
+                       break;
+               }
+       }
+
+       /* If we're just reading bitmap values, return them now */
+       if (flags & BMAP_READ) {
+               ret = 0;
+
+               if (!found)
+                       goto done;
+
+               /* First read seen bit */
+               res = bmap_read(bnode->seen, bnode->idx, idx);
+               if (res == -1) {
+                       ret = -1;
+                       goto done;
+               }
+               ret |= res << 2;
+
+               /* Then read relevant bit */
+               res = bmap_read(bnode->relv, bnode->idx, idx);
+               if (res == -1) {
+                       ret = -1;
+                       goto done;
+               }
+               ret |= res << 1;
+
+               /* Read done bit */
+               res = bmap_read(bnode->done, bnode->idx, idx);
+               if (res == -1) {
+                       ret = -1;
+                       goto done;
+               }
+
+               ret |= res;
+               goto done;
+       }
+
+       /*
+        * Take appropriate action based on whether we found the node
+        * and whether we plan to update (SET/RST), or only CHECK it.
+        *
+        *   NULL  |       Found            !Found      |
+        *  -------+------------------------------------+
+        *    SET  |     Set Bits     |  Init new node  |
+        *         |------------------+-----------------|
+        *    RST  | Clear (dispose?) |     Nothing     |
+        *  -------+------------------------------------+
+        *
+        *  CHECK  |       Found            !Found      |
+        *  -------+------------------------------------+
+        *    SET  |    Check Bits    |  Return false   |
+        *         |------------------+-----------------|
+        *    RST  |    Check Bits    |    Continue     |
+        *  -------+------------------------------------+
+        */
+
+       /* First handle setting (or checking set) bits */
+       if (flags & BMAP_ALL_SET) {
+               if (!found && !(flags & BMAP_CHECK)) {
+                       /* Insert the new node */
+                       bnode = bnode_init(bt, node_offt);
+                       if (!bnode) {
+                               ret = -1;
+                               goto done;
+                       }
+
+                       rb_link_node(&bnode->node, parent, link);
+                       rb_insert_color(&bnode->node, &bt->root);
+
+               } else if (!found && (flags & BMAP_CHECK)) {
+                       /* Looking for set bits, node didn't exist */
+                       ret = 0;
+                       goto done;
+               }
+
+               /* Set the bits. Return -1 if something goes wrong. */
+               if (!(flags & BMAP_CHECK)) {
+                       if ((flags & BMAP_SEEN_SET) &&
+                           bmap_set(bnode->seen, bnode->idx, idx, 1)) {
+                               ret = -1;
+                               goto done;
+                       }
+
+                       if ((flags & BMAP_RELV_SET) &&
+                           bmap_set(bnode->relv, bnode->idx, idx, 1)) {
+                               ret = -1;
+                               goto done;
+                       }
+
+                       if ((flags & BMAP_DONE_SET) &&
+                           bmap_set(bnode->done, bnode->idx, idx, 1)) {
+                               ret = -1;
+                               goto done;
+                       }
+
+               /* Check the bits. Return if any bits are off */
+               } else {
+                       if (flags & BMAP_SEEN_SET) {
+                               ret = bmap_chk(bnode->seen, bnode->idx, idx, 1);
+                               if (ret != 1)
+                                       goto done;
+                       }
+
+                       if (flags & BMAP_RELV_SET) {
+                               ret = bmap_chk(bnode->relv, bnode->idx, idx, 1);
+                               if (ret != 1)
+                                       goto done;
+                       }
+
+                       ret = bmap_chk(bnode->done, bnode->idx, idx, 1);
+                       if (ret != 1)
+                               goto done;
+               }
+       }
+
+       /* Now handle unsetting any bits */
+       if (found && (flags & BMAP_ALL_RST)) {
+               /* Clear the bits. Return -1 if something goes wrong. */
+               if (!(flags & BMAP_CHECK)) {
+                       if ((flags & BMAP_SEEN_RST) &&
+                           bmap_set(bnode->seen, bnode->idx, idx, 0)) {
+                               ret = -1;
+                               goto done;
+                       }
+
+                       if ((flags & BMAP_RELV_RST) &&
+                           bmap_set(bnode->relv, bnode->idx, idx, 0)) {
+                               ret = -1;
+                               goto done;
+                       }
+
+                       if ((flags & BMAP_DONE_RST) &&
+                           bmap_set(bnode->done, bnode->idx, idx, 0)) {
+                               ret = -1;
+                               goto done;
+                       }
+
+               /* Check the bits. Return if any bits are off */
+               } else {
+                       if (flags & BMAP_SEEN_RST) {
+                               ret = bmap_chk(bnode->seen, bnode->idx, idx, 0);
+                               if (ret != 1)
+                                       goto done;
+                       }
+
+                       if (flags & BMAP_RELV_RST) {
+                               ret = bmap_chk(bnode->relv, bnode->idx, idx, 0);
+                               if (ret != 1)
+                                       goto done;
+                       }
+
+                       ret = bmap_chk(bnode->done, bnode->idx, idx, 0);
+                       if (ret != 1)
+                               goto done;
+               }
+
+               /* Dispose of the node if empty */
+               if (!(flags & BMAP_CHECK) &&
+                   bitmap_empty(bnode->done, BITS_PER_NODE) &&
+                   bitmap_empty(bnode->seen, BITS_PER_NODE) &&
+                   bitmap_empty(bnode->relv, BITS_PER_NODE))
+                       bnode_dispose(bnode, parent, bt);
+       }
+
+       if (!(flags & BMAP_CHECK))
+               ret = 0;
+       else
+               ret = 1;
+
+done:
+       if (ret == -1)
+               pr_err("duet: blocks were not %s\n",
+                       (flags & BMAP_READ) ? "read" :
+                       ((flags & BMAP_CHECK) ? "checked" : "modified"));
+       spin_unlock(&bt->lock);
+       local_irq_restore(iflags);
+       return ret;
+}
+
+/*
+ * Check if we have seen this inode before. If not, check if it is relevant.
+ * Then, check whether it's done.
+ */
+static int do_bittree_check(struct duet_bittree *bt, struct duet_uuid uuid,
+                           struct duet_task *task, struct inode *inode)
+{
+       int ret, bits;
+       unsigned long long idx = UUID_IDX(uuid);
+
+       bits = __update_tree(bt, idx, BMAP_READ);
+
+       if (!(bits & 0x4)) {
+               /* We have not seen this inode before */
+               if (inode) {
+                       ret = do_find_path(task, inode, 0, NULL, 0);
+               } else if (task) {
+                       ret = duet_find_path(task, uuid, 0, NULL, 0);
+               } else {
+                       pr_err("duet: check failed, no task/inode\n");
+                       return -1;
+               }
+
+               if (!ret) {
+                       /* Mark as relevant and return not done */
+                       ret = __update_tree(bt, idx,
+                                           BMAP_SEEN_SET | BMAP_RELV_SET);
+                       if (ret != -1)
+                               ret = 0;
+
+               } else if (ret == -ENOENT) {
+                       /* Mark as irrelevant and return done */
+                       ret = __update_tree(bt, idx, BMAP_SEEN_SET);
+                       if (ret != -1)
+                               ret = 1;
+
+               } else {
+                       pr_err("duet: inode relevance undetermined\n");
+                       return -1;
+               }
+
+       } else {
+               /* We know this inode, return 1 if done, or irrelevant */
+               ret = ((bits & 0x1) || !(bits & 0x2)) ? 1 : 0;
+       }
+
+       return ret;
+}
+
+/* Checks if a given inode is done. Skips inode lookup. */
+int bittree_check_inode(struct duet_bittree *bt, struct duet_task *task,
+       struct inode *inode)
+{
+       struct duet_uuid uuid;
+
+       uuid.ino = inode->i_ino;
+       uuid.gen = inode->i_generation;
+
+       return do_bittree_check(bt, uuid, task, inode);
+}
+
+/* Checks if the given entries are done */
+int bittree_check(struct duet_bittree *bt, struct duet_uuid uuid,
+                 struct duet_task *task)
+{
+       return do_bittree_check(bt, uuid, task, NULL);
+}
+
+/* Mark done bit for given entries */
+int bittree_set(struct duet_bittree *bt, struct duet_uuid uuid)
+{
+       return __update_tree(bt, UUID_IDX(uuid), BMAP_DONE_SET);
+}
+
+/* Unmark done bit for given entries */
+int bittree_reset(struct duet_bittree *bt, struct duet_uuid uuid)
+{
+       return __update_tree(bt, UUID_IDX(uuid), BMAP_DONE_RST);
+}
+
+int bittree_print(struct duet_task *task)
+{
+       struct bmap_rbnode *bnode = NULL;
+       struct rb_node *node;
+       unsigned long iflags;
+
+       local_irq_save(iflags);
+       spin_lock(&task->bittree.lock);
+       pr_info("duet: Printing task bittree\n");
+       node = rb_first(&task->bittree.root);
+       while (node) {
+               bnode = rb_entry(node, struct bmap_rbnode, node);
+
+               /* Print node information */
+               pr_info("duet: Node key = %llu\n", bnode->idx);
+               pr_info("duet:   Done bits set: %d out of %d\n",
+                       bitmap_weight(bnode->done, BITS_PER_NODE),
+                       BITS_PER_NODE);
+               pr_info("duet:   Relv bits set: %d out of %d\n",
+                       bitmap_weight(bnode->relv, BITS_PER_NODE),
+                       BITS_PER_NODE);
+               pr_info("duet:   Seen bits set: %d out of %d\n",
+                       bitmap_weight(bnode->seen, BITS_PER_NODE),
+                       BITS_PER_NODE);
+
+               node = rb_next(node);
+       }
+       spin_unlock(&task->bittree.lock);
+       local_irq_restore(iflags);
+
+       pr_info("duet: Task #%d bitmap has %d out of %lu bits set\n",
+               task->id, bitmap_weight(task->bucket_bmap,
+               duet_env.itm_hash_size), duet_env.itm_hash_size);
+
+       return 0;
+}
+
+void bittree_init(struct duet_bittree *bittree)
+{
+       spin_lock_init(&bittree->lock);
+       bittree->root = RB_ROOT;
+#ifdef CONFIG_DUET_STATS
+       bittree->statcur = bittree->statmax = 0;
+#endif /* CONFIG_DUET_STATS */
+}
+
+void bittree_destroy(struct duet_bittree *bittree)
+{
+       struct rb_node *rbnode;
+       struct bmap_rbnode *bnode;
+
+       while (!RB_EMPTY_ROOT(&bittree->root)) {
+               rbnode = rb_first(&bittree->root);
+               bnode = rb_entry(rbnode, struct bmap_rbnode, node);
+               bnode_dispose(bnode, rbnode, bittree);
+       }
+}
diff --git a/mm/duet/common.h b/mm/duet/common.h
new file mode 100644
index 0000000..1dac66b
--- /dev/null
+++ b/mm/duet/common.h
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2016 George Amvrosiadis.  All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License v2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+#ifndef _COMMON_H
+#define _COMMON_H
+
+#include <linux/fs.h>
+#include <linux/mount.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/bitmap.h>
+#include <linux/rculist.h>
+#include <linux/syscalls.h>
+#include <linux/duet.h>
+
+#ifdef DUET_DEBUG
+#define duet_dbg(...)  pr_info(__VA_ARGS__)
+#else
+#define duet_dbg(...)
+#endif
+
+/*
+ * Duet can be state-based, and/or event-based.
+ *
+ * Event-based Duet monitors events that occurred on a page, during its
+ * time in the page cache: ADDED, REMOVED, DIRTY, and FLUSHED.
+ *
+ * State-based Duet monitors changes in the page cache since the last time
+ * a notification was sent to the interested application. Registering for
+ * EXIST informs the application of page additions or removals from the cache
+ * (i.e. ADDED and REMOVED events cancel each other out if the application
+ * hasn't been told in the meantime). Registering for MODIFIED events is a
+ * similar model, where unreported DIRTY and FLUSHED events cancel each other.
+ */
+#define DUET_PAGE_ADDED                0x0001
+#define DUET_PAGE_REMOVED      0x0002
+#define DUET_PAGE_DIRTY                0x0004
+#define DUET_PAGE_FLUSHED      0x0008
+#define DUET_PAGE_MODIFIED     0x0010
+#define DUET_PAGE_EXISTS       0x0020
+#define DUET_FD_NONBLOCK       0x0040
+
+/* Used only for page state */
+#define DUET_MASK_VALID                0x8000
+
+/* Some useful flags for clearing bitmaps */
+#define BMAP_SEEN      0x1
+#define BMAP_RELV      0x2
+#define BMAP_DONE      0x4
+
+#define DUET_DEF_NUMTASKS      8
+#define DUET_INODE_FREEING     (I_WILL_FREE | I_FREEING | I_CLEAR)
+
+enum {
+       DUET_STATUS_OFF = 0,
+       DUET_STATUS_ON,
+       DUET_STATUS_INIT,
+       DUET_STATUS_CLEAN,
+};
+
+/*
+ * Item struct returned for processing.
+ * The UUID currently consists of the inode number, the inode generation
+ * (to help us identify cases of inode reuse), and the task id.
+ * For state-based duet, we mark a page if it EXISTS or is MODIFIED.
+ * For event-based duet, we mark a page added, removed, dirtied, and/or 
flushed.
+ * Acceptable event combinations will differ based on the task's subscription.
+ */
+struct duet_uuid {
+       unsigned long   ino;
+       __u32           gen;
+       __u8            tid;
+};
+
+struct duet_item {
+       struct duet_uuid        uuid;
+       unsigned long           idx;
+       __u16                   state;
+};
+
+/*
+ * Red-black bitmap tree node.
+ * Represents the range starting from idx. For block tasks, only the done
+ * bitmap is used. For file tasks, the seen and relv (relevant) bitmaps are
+ * also used. The semantics of different states are listed below, where an
+ * item can be in the unknown state due to a bitmap reset, or because it hasn't
+ * been encountered yet.
+ * - !SEEN && !RELV && !DONE: Item in unknown state
+ * - !SEEN && !RELV &&  DONE: Item processed, but in unknown state
+ * -  SEEN && !RELV && !DONE: Item not relevant to the task
+ * -  SEEN &&  RELV && !DONE: Item is relevant, but not processed
+ * -  SEEN &&  RELV &&  DONE: Item is relevant, and has already been processed
+ */
+struct bmap_rbnode {
+       __u64           idx;
+       struct rb_node  node;
+       unsigned long   *seen;
+       unsigned long   *relv;
+       unsigned long   *done;
+};
+
+struct item_hnode {
+       struct hlist_bl_node    node;
+       struct duet_item        item;
+       __u8                    refcount;
+       __u16                   *state;         /* One entry per task */
+};
+
+struct duet_bittree {
+       spinlock_t              lock;
+       struct rb_root          root;
+#ifdef CONFIG_DUET_STATS
+       __u64                   statcur;        /* Cur # of BitTree nodes */
+       __u64                   statmax;        /* Max # of BitTree nodes */
+#endif /* CONFIG_DUET_STATS */
+};
+
+struct duet_task {
+       __u8                    id;             /* Internal task ID */
+
+       int                     fd;
+       struct filename         *name;
+       __u32                   evtmask;        /* Mask of subscribed events */
+       struct path             *regpath;       /* Registered path */
+       char                    *regpathname;   /* Registered path name */
+       __u16                   regpathlen;     /* Length of path name */
+
+       /* Data structures linking task to framework */
+       struct list_head        task_list;
+       wait_queue_head_t       cleaner_queue;
+       atomic_t                refcount;
+       char                    *pathbuf;       /* Buffer for getpath */
+       struct duet_bittree     bittree;        /* Progress bitmap */
+       wait_queue_head_t       event_queue;    /* for read() and poll() */
+
+       /* Hash table bucket bitmap */
+       spinlock_t              bbmap_lock;
+       unsigned long           *bucket_bmap;
+       unsigned long           bmap_cursor;
+};
+
+struct duet_info {
+       atomic_t                status;
+       __u8                    numtasks;       /* Number of concurrent tasks */
+
+       /*
+        * Access to the task list is synchronized via a mutex. However, any
+        * operations that are on-going for a task (e.g. fetch) will increase
+        * its refcount. This refcount is consulted when disposing of the task.
+        */
+       struct mutex            task_list_mutex;
+       struct list_head        tasks;
+
+       /* ItemTable -- Global page state hash table */
+       struct hlist_bl_head    *itm_hash_table;
+       unsigned long           itm_hash_size;
+       unsigned long           itm_hash_shift;
+       unsigned long           itm_hash_mask;
+};
+
+extern struct duet_info duet_env;
+
+/* hook.c */
+void duet_hook(__u16 evtcode, void *data);
+
+/* hash.c */
+int hash_init(void);
+int hash_add(struct duet_task *task, struct duet_uuid uuid,
+            unsigned long idx, __u16 evtmask, short in_scan);
+int hash_fetch(struct duet_task *task, struct duet_item *itm);
+void hash_print(struct duet_task *task);
+
+/* task.c -- not in linux/duet.h */
+struct duet_task *duet_find_task(__u8 id);
+void duet_task_dispose(struct duet_task *task);
+
+/* path.c */
+int do_find_path(struct duet_task *task, struct inode *inode,
+                int getpath, char *buf, int bufsize);
+int duet_find_path(struct duet_task *task, struct duet_uuid uuid,
+                  int getpath, char *buf, int bufsize);
+
+/* bittree.c */
+int bittree_check_inode(struct duet_bittree *bt, struct duet_task *task,
+                       struct inode *inode);
+int bittree_check(struct duet_bittree *bt, struct duet_uuid uuid,
+                 struct duet_task *task);
+int bittree_set(struct duet_bittree *bt, struct duet_uuid uuid);
+int bittree_reset(struct duet_bittree *bt, struct duet_uuid uuid);
+int bittree_print(struct duet_task *task);
+void bittree_init(struct duet_bittree *bittree);
+void bittree_destroy(struct duet_bittree *bittree);
+
+/* init.c */
+int duet_online(void);
+
+/* debug.c */
+int duet_print_bmap(__u8 id);
+int duet_print_item(__u8 id);
+int duet_print_list(struct duet_status_args __user *arg);
+
+#endif /* _COMMON_H */
diff --git a/mm/duet/debug.c b/mm/duet/debug.c
new file mode 100644
index 0000000..77a47b6
--- /dev/null
+++ b/mm/duet/debug.c
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2016 George Amvrosiadis.  All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License v2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include "common.h"
+#include "syscall.h"
+
+/* Do a preorder print of the BitTree */
+int duet_print_bmap(__u8 id)
+{
+       struct duet_task *task;
+
+       task = duet_find_task(id);
+       if (!task)
+               return -ENOENT;
+
+       if (bittree_print(task)) {
+               pr_err("duet: failed to print BitTree for task #%d\n",
+                       task->id);
+               return -1;
+       }
+
+       /* decref and wake up cleaner if needed */
+       if (atomic_dec_and_test(&task->refcount))
+               wake_up(&task->cleaner_queue);
+
+       return 0;
+}
+
+/* Do a preorder print of the global hash table */
+int duet_print_item(__u8 id)
+{
+       struct duet_task *task;
+
+       task = duet_find_task(id);
+       if (!task)
+               return -ENOENT;
+
+       hash_print(task);
+
+       /* decref and wake up cleaner if needed */
+       if (atomic_dec_and_test(&task->refcount))
+               wake_up(&task->cleaner_queue);
+
+       return 0;
+}
+
+int duet_print_list(struct duet_status_args __user *arg)
+{
+       int i = 0;
+       struct duet_task *cur;
+       struct duet_status_args argh, *argp;
+
+       /* Copy in task list header (again) */
+       if (copy_from_user(&argh, arg, sizeof(argh)))
+               return -EFAULT;
+
+       /* Copy in entire task list */
+       argp = memdup_user(arg, sizeof(*argp) + (argh.numtasks *
+                               sizeof(struct duet_task_attrs)));
+       if (IS_ERR(argp))
+               return PTR_ERR(argp);
+
+       /* Copy the info for the first numtasks */
+       mutex_lock(&duet_env.task_list_mutex);
+       list_for_each_entry(cur, &duet_env.tasks, task_list) {
+               argp->tasks[i].id = cur->id;
+               argp->tasks[i].fd = cur->fd;
+               memcpy(argp->tasks[i].name, cur->name->name, NAME_MAX);
+               argp->tasks[i].regmask = cur->evtmask;
+               memcpy(argp->tasks[i].path, cur->regpathname, cur->regpathlen);
+               i++;
+               if (i == argp->numtasks)
+                       break;
+       }
+       mutex_unlock(&duet_env.task_list_mutex);
+
+       /* Copy out entire task list */
+       if (copy_to_user(arg, argp, sizeof(*argp) + (argp->numtasks *
+                        sizeof(struct duet_task_attrs)))) {
+               pr_err("duet_print_list: failed to copy out list\n");
+               kfree(argp);
+               return -EINVAL;
+       }
+
+       duet_dbg("duet_print_list: success sending task list\n");
+       kfree(argp);
+       return 0;
+}
diff --git a/mm/duet/hash.c b/mm/duet/hash.c
new file mode 100644
index 0000000..c2644d6
--- /dev/null
+++ b/mm/duet/hash.c
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2016 George Amvrosiadis.  All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License v2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/hash.h>
+#include "common.h"
+
+#define DUET_NEGATE_EXISTS     (DUET_PAGE_ADDED | DUET_PAGE_REMOVED)
+#define DUET_NEGATE_MODIFIED   (DUET_PAGE_DIRTY | DUET_PAGE_FLUSHED)
+
+/*
+ * Page state is retained in a global hash table shared by all tasks.
+ * Indexing is based on the page's inode number and offset.
+ */
+
+static unsigned long hash(unsigned long ino, unsigned long idx)
+{
+       unsigned long h;
+
+       h = (idx * ino ^ (GOLDEN_RATIO_PRIME + idx)) / L1_CACHE_BYTES;
+       h = h ^ ((h ^ GOLDEN_RATIO_PRIME) >> duet_env.itm_hash_shift);
+       return h & duet_env.itm_hash_mask;
+}
+
+int hash_init(void)
+{
+       /* Allocate power-of-2 number of buckets */
+       duet_env.itm_hash_shift = ilog2(totalram_pages);
+       duet_env.itm_hash_size = 1 << duet_env.itm_hash_shift;
+       duet_env.itm_hash_mask = duet_env.itm_hash_size - 1;
+
+       pr_debug("duet: allocated global hash table (%lu buckets)\n",
+               duet_env.itm_hash_size);
+       duet_env.itm_hash_table = vmalloc(sizeof(struct hlist_bl_head) *
+                                               duet_env.itm_hash_size);
+       if (!duet_env.itm_hash_table)
+               return 1;
+
+       memset(duet_env.itm_hash_table, 0, sizeof(struct hlist_bl_head) *
+                                               duet_env.itm_hash_size);
+       return 0;
+}
+
+/* Deallocate a hash table node */
+static void hnode_destroy(struct item_hnode *itnode)
+{
+       kfree(itnode->state);
+       kfree(itnode);
+}
+
+/* Allocate and initialize a new hash table node */
+static struct item_hnode *hnode_init(struct duet_uuid uuid, unsigned long idx)
+{
+       struct item_hnode *itnode = NULL;
+
+       itnode = kzalloc(sizeof(struct item_hnode), GFP_NOWAIT);
+       if (!itnode)
+               return NULL;
+
+       itnode->state = kcalloc(duet_env.numtasks, sizeof(*(itnode->state)),
+                               GFP_NOWAIT);
+       if (!(itnode->state)) {
+               pr_err("duet: failed to allocate hash node state\n");
+               kfree(itnode);
+               return NULL;
+       }
+
+       (itnode->item).uuid = uuid;
+       (itnode->item).idx = idx;
+       itnode->refcount++;
+
+       return itnode;
+}
+
+/* Add one event into the hash table */
+int hash_add(struct duet_task *task, struct duet_uuid uuid, unsigned long idx,
+       __u16 evtmask, short in_scan)
+{
+       __u16 curmask = 0;
+       short found = 0;
+       unsigned long bnum, flags;
+       struct hlist_bl_head *b;
+       struct hlist_bl_node *n;
+       struct item_hnode *itnode;
+
+       evtmask &= task->evtmask;
+
+       /* Get the bucket */
+       bnum = hash(uuid.ino, idx);
+       b = duet_env.itm_hash_table + bnum;
+       local_irq_save(flags);
+       hlist_bl_lock(b);
+
+       /* Lookup the item in the bucket */
+       hlist_bl_for_each_entry(itnode, n, b, node) {
+               if ((itnode->item).uuid.ino == uuid.ino &&
+                   (itnode->item).uuid.gen == uuid.gen &&
+                   (itnode->item).idx == idx) {
+                       found = 1;
+                       break;
+               }
+       }
+
+       duet_dbg("duet: %s hash node (tid %d, ino%lu, gen%lu, idx%lu)\n",
+               found ? (in_scan ? "replacing" : "updating") : "inserting",
+               uuid.tid, uuid.ino, uuid.gen, idx);
+
+       if (found) {
+               curmask = itnode->state[task->id];
+
+               /* Only up the refcount if we are adding a new mask */
+               if (!(curmask & DUET_MASK_VALID) || in_scan) {
+                       if (!in_scan)
+                               itnode->refcount++;
+                       curmask = evtmask | DUET_MASK_VALID;
+                       goto check_dispose;
+               }
+
+               curmask |= evtmask | DUET_MASK_VALID;
+
+               /* Negate previous events and remove if needed */
+               if ((task->evtmask & DUET_PAGE_EXISTS) &&
+                  ((curmask & DUET_NEGATE_EXISTS) == DUET_NEGATE_EXISTS))
+                       curmask &= ~DUET_NEGATE_EXISTS;
+
+               if ((task->evtmask & DUET_PAGE_MODIFIED) &&
+                  ((curmask & DUET_NEGATE_MODIFIED) == DUET_NEGATE_MODIFIED))
+                       curmask &= ~DUET_NEGATE_MODIFIED;
+
+check_dispose:
+               if ((curmask == DUET_MASK_VALID) && (itnode->refcount == 1)) {
+                       if (itnode->refcount != 1) {
+                               itnode->state[task->id] = 0;
+                       } else {
+                               hlist_bl_del(&itnode->node);
+                               hnode_destroy(itnode);
+                       }
+
+                       /* Are we still interested in this bucket? */
+                       hlist_bl_for_each_entry(itnode, n, b, node) {
+                               if (itnode->state[task->id] & DUET_MASK_VALID) {
+                                       found = 1;
+                                       break;
+                               }
+                       }
+
+                       if (!found)
+                               clear_bit(bnum, task->bucket_bmap);
+               } else {
+                       itnode->state[task->id] = curmask;
+
+                       /* Update bitmap */
+                       set_bit(bnum, task->bucket_bmap);
+               }
+       } else if (!found) {
+               if (!evtmask)
+                       goto done;
+
+               itnode = hnode_init(uuid, idx);
+               if (!itnode)
+                       return 1;
+
+               itnode->state[task->id] = evtmask | DUET_MASK_VALID;
+               hlist_bl_add_head(&itnode->node, b);
+
+               /* Update bitmap */
+               set_bit(bnum, task->bucket_bmap);
+       }
+
+done:
+       hlist_bl_unlock(b);
+       local_irq_restore(flags);
+       return 0;
+}
+
+/* Fetch one item for a given task. Return found (1), empty (0), error (-1) */
+int hash_fetch(struct duet_task *task, struct duet_item *itm)
+{
+       int found;
+       unsigned long bnum, flags;
+       struct hlist_bl_head *b;
+       struct hlist_bl_node *n;
+       struct item_hnode *itnode;
+
+       local_irq_save(flags);
+again:
+       spin_lock(&task->bbmap_lock);
+       bnum = find_next_bit(task->bucket_bmap, duet_env.itm_hash_size,
+                            task->bmap_cursor);
+
+       if (bnum == duet_env.itm_hash_size) {
+               /* Reached end of bitmap */
+               found = 0;
+
+               if (task->bmap_cursor != 0) {
+                       /* Started part way, try again */
+                       bnum = find_next_bit(task->bucket_bmap,
+                                            task->bmap_cursor, 0);
+
+                       if (bnum != task->bmap_cursor)
+                               found = 1;
+               }
+
+               if (!found) {
+                       spin_unlock(&task->bbmap_lock);
+                       local_irq_restore(flags);
+                       return 1;
+               }
+       }
+
+       task->bmap_cursor = bnum;
+       clear_bit(bnum, task->bucket_bmap);
+       spin_unlock(&task->bbmap_lock);
+       b = duet_env.itm_hash_table + bnum;
+
+       /* Grab first item from bucket */
+       hlist_bl_lock(b);
+       if (!b->first) {
+               pr_err("duet: empty hash bucket marked in bitmap\n");
+               hlist_bl_unlock(b);
+               goto again;
+       }
+
+       found = 0;
+       hlist_bl_for_each_entry(itnode, n, b, node) {
+               if (itnode->state[task->id] & DUET_MASK_VALID) {
+                       *itm = itnode->item;
+                       itm->uuid.tid = task->id;
+                       itm->state = itnode->state[task->id] &
+                                    (~DUET_MASK_VALID);
+
+                       itnode->refcount--;
+                       /* Free or update node */
+                       if (!itnode->refcount) {
+                               hlist_bl_del(n);
+                               hnode_destroy(itnode);
+                       } else {
+                               itnode->state[task->id] = 0;
+                       }
+
+                       found = 1;
+                       break;
+               }
+       }
+
+       if (!found) {
+               hlist_bl_unlock(b);
+               goto again;
+       }
+
+       /* Are we still interested in this bucket? */
+       found = 0;
+       hlist_bl_for_each_entry(itnode, n, b, node) {
+               if (itnode->state[task->id] & DUET_MASK_VALID) {
+                       found = 1;
+                       break;
+               }
+       }
+
+       if (found)
+               set_bit(bnum, task->bucket_bmap);
+
+       hlist_bl_unlock(b);
+       local_irq_restore(flags);
+       return 0;
+}
+
+/* Warning: expensive printing function. Use with care. */
+void hash_print(struct duet_task *task)
+{
+       unsigned long loop, count, start, end, buckets, flags;
+       unsigned long long nodes, tnodes;
+       struct hlist_bl_head *b;
+       struct hlist_bl_node *n;
+       struct item_hnode *itnode;
+
+       count = duet_env.itm_hash_size / 100;
+       tnodes = nodes = buckets = start = end = 0;
+       pr_info("duet: Printing hash table\n");
+       for (loop = 0; loop < duet_env.itm_hash_size; loop++) {
+               if (loop - start >= count) {
+                       pr_info("duet:   Buckets %lu - %lu: %llu nodes (task: 
%llu)\n",
+                               start, end, nodes, tnodes);
+                       start = end = loop;
+                       nodes = tnodes = 0;
+               }
+
+               /* Count bucket nodes */
+               b = duet_env.itm_hash_table + loop;
+               local_irq_save(flags);
+               hlist_bl_lock(b);
+               hlist_bl_for_each_entry(itnode, n, b, node) {
+                       nodes++;
+                       if (itnode->state[task->id] & DUET_MASK_VALID)
+                               tnodes++;
+               }
+               hlist_bl_unlock(b);
+               local_irq_restore(flags);
+
+               end = loop;
+       }
+
+       if (start != loop - 1)
+               pr_info("duet:   Buckets %lu - %lu: %llu nodes (task: %llu)\n",
+                       start, end, nodes, tnodes);
+}
diff --git a/mm/duet/hook.c b/mm/duet/hook.c
new file mode 100644
index 0000000..3ac89f5
--- /dev/null
+++ b/mm/duet/hook.c
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2016 George Amvrosiadis.  All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License v2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include "common.h"
+
+/* Handle an event. We're in RCU context so whatever happens, stay awake! */
+void duet_hook(__u16 evtcode, void *data)
+{
+       struct page *page = NULL;
+       struct inode *inode = NULL;
+       struct duet_task *cur;
+       unsigned long page_idx = 0;
+       struct duet_uuid uuid;
+
+       /* Duet must be online */
+       if (!duet_online())
+               return;
+
+       /* Handle page event */
+       page = (struct page *)data;
+
+       /* Duet must be online, and the page must belong to a valid mapping */
+       if (!page || !page_mapping(page)) {
+               duet_dbg("duet: dropped event %x due to NULL mapping\n",
+                       evtcode);
+               return;
+       }
+
+       inode = page_mapping(page)->host;
+       page_idx = page->index;
+
+       /* Check that we're referring to an actual inode and get its UUID */
+       if (!inode)
+               return;
+
+       uuid.ino = inode->i_ino;
+       uuid.gen = inode->i_generation;
+
+       /* Verify that the inode does not belong to a special file */
+       if (!S_ISREG(inode->i_mode) && !S_ISDIR(inode->i_mode))
+               return;
+
+       if (!inode->i_ino) {
+               pr_err("duet: inode not initialized\n");
+               return;
+       }
+
+       /* Look for tasks interested in this event type and invoke callbacks */
+       rcu_read_lock();
+       list_for_each_entry_rcu(cur, &duet_env.tasks, task_list) {
+               struct super_block *sb = cur->regpath->mnt->mnt_sb;
+
+               /* Verify that the event refers to the fs we're interested in */
+               if (sb && sb != inode->i_sb)
+                       continue;
+
+               duet_dbg("duet: rcvd event %x on (ino %lu, gen %lu, idx %lu)\n",
+                       evtcode, uuid.ino, uuid.gen, page_idx);
+
+               /* Use the inode bitmap to filter out event if applicable */
+               if (bittree_check_inode(&cur->bittree, cur, inode) == 1)
+                       continue;
+
+               /* Update the hash table */
+               if (hash_add(cur, uuid, page_idx, evtcode, 0))
+                       pr_err("duet: hash table add failed\n");
+
+               wake_up(&cur->event_queue);
+       }
+       rcu_read_unlock();
+}
diff --git a/mm/duet/init.c b/mm/duet/init.c
new file mode 100644
index 0000000..a9e5ea1
--- /dev/null
+++ b/mm/duet/init.c
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2016 George Amvrosiadis.  All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License v2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include "common.h"
+#include "syscall.h"
+
+struct duet_info duet_env;
+duet_hook_t *duet_hook_fp;
+EXPORT_SYMBOL(duet_hook_fp);
+
+int duet_online(void)
+{
+       return (atomic_read(&duet_env.status) == DUET_STATUS_ON);
+}
+
+int duet_bootstrap(__u16 numtasks)
+{
+       if (atomic_cmpxchg(&duet_env.status, DUET_STATUS_OFF, DUET_STATUS_INIT)
+           != DUET_STATUS_OFF) {
+               pr_err("duet: framework on, bootstrap aborted\n");
+               return 1;
+       }
+
+       duet_env.numtasks = (numtasks ? numtasks : DUET_DEF_NUMTASKS);
+
+       /* Initialize global hash table */
+       if (hash_init()) {
+               pr_err("duet: failed to initialize hash table\n");
+               return 1;
+       }
+
+       /* Initialize task list */
+       INIT_LIST_HEAD(&duet_env.tasks);
+       mutex_init(&duet_env.task_list_mutex);
+       atomic_set(&duet_env.status, DUET_STATUS_ON);
+
+       rcu_assign_pointer(duet_hook_fp, duet_hook);
+       synchronize_rcu();
+       return 0;
+}
+
+int duet_shutdown(void)
+{
+       struct duet_task *task;
+
+       if (atomic_cmpxchg(&duet_env.status, DUET_STATUS_ON, DUET_STATUS_CLEAN)
+           != DUET_STATUS_ON) {
+               pr_err("duet: framework off, shutdown aborted\n");
+               return 1;
+       }
+
+       rcu_assign_pointer(duet_hook_fp, NULL);
+       synchronize_rcu();
+
+       /* Remove all tasks */
+       mutex_lock(&duet_env.task_list_mutex);
+       while (!list_empty(&duet_env.tasks)) {
+               task = list_entry_rcu(duet_env.tasks.next, struct duet_task,
+                               task_list);
+               list_del_rcu(&task->task_list);
+               mutex_unlock(&duet_env.task_list_mutex);
+
+               /* Make sure everyone's let go before we free it */
+               synchronize_rcu();
+               wait_event(task->cleaner_queue,
+                       atomic_read(&task->refcount) == 0);
+               duet_task_dispose(task);
+
+               mutex_lock(&duet_env.task_list_mutex);
+       }
+       mutex_unlock(&duet_env.task_list_mutex);
+
+       /* Destroy global hash table */
+       vfree((void *)duet_env.itm_hash_table);
+
+       INIT_LIST_HEAD(&duet_env.tasks);
+       mutex_destroy(&duet_env.task_list_mutex);
+       atomic_set(&duet_env.status, DUET_STATUS_OFF);
+       return 0;
+}
+
+SYSCALL_DEFINE2(duet_status, u16, flags, struct duet_status_args __user *, arg)
+{
+       int ret = 0;
+       struct duet_status_args *sa;
+
+       if (!capable(CAP_SYS_ADMIN))
+               return -EPERM;
+
+       sa = memdup_user(arg, sizeof(*sa));
+       if (IS_ERR(sa))
+               return PTR_ERR(sa);
+
+       /* For now, we only support one struct size */
+       if (sa->size != sizeof(*sa)) {
+               pr_err("duet_status: invalid args struct size (%u)\n",
+                       sa->size);
+               ret = -EINVAL;
+               goto done;
+       }
+
+       /* If we're cleaning up, only allow ops that affect Duet status */
+       if (atomic_read(&duet_env.status) != DUET_STATUS_ON && !(flags &
+           (DUET_STATUS_START | DUET_STATUS_STOP | DUET_STATUS_REPORT))) {
+               pr_err("duet_status: ops rejected during shutdown\n");
+               ret = -EINVAL;
+               goto done;
+       }
+
+       switch (flags) {
+       case DUET_STATUS_START:
+               ret = duet_bootstrap(sa->maxtasks);
+
+               if (ret)
+                       pr_err("duet: failed to enable framework\n");
+               else
+                       pr_info("duet: framework enabled\n");
+
+               break;
+
+       case DUET_STATUS_STOP:
+               ret = duet_shutdown();
+
+               if (ret)
+                       pr_err("duet: failed to disable framework\n");
+               else
+                       pr_info("duet: framework disabled\n");
+
+               break;
+
+       case DUET_STATUS_REPORT:
+               ret = duet_online();
+               break;
+
+       case DUET_STATUS_PRINT_BMAP:
+               ret = duet_print_bmap(sa->id);
+               break;
+
+       case DUET_STATUS_PRINT_ITEM:
+               ret = duet_print_item(sa->id);
+               break;
+
+       case DUET_STATUS_PRINT_LIST:
+               ret = duet_print_list(arg);
+               goto done;
+
+       default:
+               pr_info("duet_status: invalid flags\n");
+               ret = -EINVAL;
+               goto done;
+       }
+
+       if (copy_to_user(arg, sa, sizeof(*sa))) {
+               pr_err("duet_status: copy_to_user failed\n");
+               ret = -EINVAL;
+               goto done;
+       }
+
+done:
+       kfree(sa);
+       return ret;
+}
diff --git a/mm/duet/path.c b/mm/duet/path.c
new file mode 100644
index 0000000..103ab42
--- /dev/null
+++ b/mm/duet/path.c
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2016 George Amvrosiadis.  All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License v2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include "common.h"
+#include "syscall.h"
+
+/* Scan through the page cache for a given inode */
+static int find_get_inode(struct super_block *sb, struct duet_uuid c_uuid,
+       struct inode **c_inode)
+{
+       struct inode *inode = NULL;
+
+       *c_inode = NULL;
+       spin_lock(&sb->s_inode_list_lock);
+       list_for_each_entry(inode, &sb->s_inodes, i_sb_list) {
+               spin_lock(&inode->i_lock);
+               if (!*c_inode && inode->i_ino == c_uuid.ino &&
+                   inode->i_generation == c_uuid.gen &&
+                   !(inode->i_state & DUET_INODE_FREEING)) {
+                       atomic_inc(&inode->i_count);
+                       *c_inode = inode;
+                       spin_unlock(&inode->i_lock);
+                       spin_unlock(&sb->s_inode_list_lock);
+                       return 0;
+               }
+               spin_unlock(&inode->i_lock);
+       }
+       spin_unlock(&sb->s_inode_list_lock);
+
+       /* We shouldn't get here unless we failed */
+       return 1;
+}
+
+int do_find_path(struct duet_task *task, struct inode *inode, int getpath,
+       char *buf, int bufsize)
+{
+       int len;
+       char *p;
+       struct path path;
+       struct dentry *alias, *i_dentry = NULL;
+
+       if (!task) {
+               pr_err("do_find_path: invalid task\n");
+               return -EINVAL;
+       }
+
+       if (getpath)
+               buf[0] = '\0';
+
+       /* Get the path for at least one alias of the inode */
+       if (hlist_empty(&inode->i_dentry))
+               return -ENOENT;
+
+       hlist_for_each_entry(alias, &inode->i_dentry, d_u.d_alias) {
+               if (IS_ROOT(alias) && (alias->d_flags & DCACHE_DISCONNECTED))
+                       continue;
+
+               i_dentry = alias;
+
+               /* Now get the path */
+               len = PATH_MAX;
+               memset(task->pathbuf, 0, len);
+               path.mnt = task->regpath->mnt;
+               path.dentry = i_dentry;
+
+               p = d_path(&path, task->pathbuf, len);
+               if (IS_ERR(p)) {
+                       pr_err("do_find_path: d_path failed\n");
+                       continue;
+               } else if (!p) {
+                       duet_dbg("do_find_path: dentry not found\n");
+                       continue;
+               }
+
+               /* Is this path of interest? */
+               if (memcmp(task->regpathname, p, task->regpathlen - 1)) {
+                       duet_dbg("do_find_path: no common ancestor\n");
+                       continue;
+               }
+
+               /* Got one. If it fits, return it */
+               if (getpath && (bufsize < len - (p - task->pathbuf)))
+                       return -ENOMEM;
+
+               duet_dbg("do_find_path: got %s\n", p);
+               if (getpath)
+                       memcpy(buf, p, len - (p - task->pathbuf));
+
+               return 0;
+       }
+
+       /* We only get here if we got nothing */
+       return -ENOENT;
+}
+
+int duet_find_path(struct duet_task *task, struct duet_uuid uuid, int getpath,
+       char *buf, int bufsize)
+{
+       int ret = 0;
+       struct inode *ino;
+
+       if (!task) {
+               pr_err("duet_find_path: invalid task\n");
+               return -EINVAL;
+       }
+
+       /* First, we need to find struct inode for child and parent */
+       if (find_get_inode(task->regpath->mnt->mnt_sb, uuid, &ino)) {
+               duet_dbg("duet_find_path: child inode not found\n");
+               return -ENOENT;
+       }
+
+       ret = do_find_path(task, ino, getpath, buf, bufsize);
+
+       iput(ino);
+       return ret;
+}
+
+SYSCALL_DEFINE3(duet_get_path, struct duet_uuid_arg __user *, uuid,
+               char __user *, pathbuf, int, pathbufsize)
+{
+       int ret = 0;
+       struct duet_uuid_arg *ua;
+       struct duet_task *task;
+       char *buf;
+
+       if (!capable(CAP_SYS_ADMIN))
+               return -EPERM;
+
+       if (!duet_online())
+               return -ESRCH;
+
+       /* Do some basic sanity checking */
+       if (!uuid || pathbufsize <= 0)
+               return -EINVAL;
+
+       buf = kcalloc(pathbufsize, sizeof(char), GFP_KERNEL);
+       if (!buf)
+               return -ENOMEM;
+
+       ua = memdup_user(uuid, sizeof(*uuid));
+       if (IS_ERR(ua)) {
+               kfree(buf);
+               return PTR_ERR(ua);
+       }
+
+       /* For now, we only support one struct size */
+       if (ua->size != sizeof(*ua)) {
+               pr_err("duet_get_path: invalid args struct size (%u)\n",
+                       ua->size);
+               ret = -EINVAL;
+               goto done;
+       }
+
+       task = duet_find_task(ua->uuid.tid);
+       if (!task) {
+               ret = -ENOENT;
+               goto done;
+       }
+
+       ret = duet_find_path(task, ua->uuid, 1, buf, pathbufsize);
+
+       if (!ret && copy_to_user(pathbuf, buf, pathbufsize))
+               ret = -EFAULT;
+
+       /* decref and wake up cleaner if needed */
+       if (atomic_dec_and_test(&task->refcount))
+               wake_up(&task->cleaner_queue);
+
+done:
+       kfree(ua);
+       kfree(buf);
+       return ret;
+}
diff --git a/mm/duet/syscall.h b/mm/duet/syscall.h
new file mode 100644
index 0000000..1bc6830
--- /dev/null
+++ b/mm/duet/syscall.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2016 George Amvrosiadis.  All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License v2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include "common.h"
+
+/* Status syscall flags */
+#define DUET_STATUS_START      0x0001
+#define DUET_STATUS_STOP       0x0002
+#define DUET_STATUS_REPORT     0x0004
+#define DUET_STATUS_PRINT_BMAP 0x0008
+#define DUET_STATUS_PRINT_ITEM 0x0010
+#define DUET_STATUS_PRINT_LIST 0x0020
+
+struct duet_task_attrs {
+       __u8    id;
+       int     fd;
+       char    name[NAME_MAX];
+       __u32   regmask;
+       char    path[PATH_MAX];
+};
+
+struct duet_status_args {
+       __u32 size;                                             /* in */
+       union {
+               /* DUET_START args */
+               struct {
+                       __u16 maxtasks;                         /* in */
+               };
+
+               /* DUET_PRINT_BIT, DUET_PRINT_ITEM args */
+               struct {
+                       __u8 id;                                /* in */
+               };
+
+               /* DUET_PRINT_LIST args */
+               struct {
+                       __u16 numtasks;                         /* out */
+                       struct duet_task_attrs tasks[0];        /* out */
+               };
+       };
+};
+
+/* Bmap syscall flags */
+#define DUET_BMAP_SET          0x0001
+#define DUET_BMAP_RESET                0x0002
+#define DUET_BMAP_CHECK                0x0004
+
+struct duet_uuid_arg {
+       __u32                   size;
+       struct duet_uuid        uuid;
+};
diff --git a/mm/duet/task.c b/mm/duet/task.c
new file mode 100644
index 0000000..c91fad3
--- /dev/null
+++ b/mm/duet/task.c
@@ -0,0 +1,584 @@
+/*
+ * Copyright (C) 2016 George Amvrosiadis.  All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License v2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/namei.h>
+#include <linux/anon_inodes.h>
+#include "common.h"
+#include "syscall.h"
+
+/*
+ * To synchronize access to the task list and structures without compromising
+ * scalability, a two-level approach is used. At the task list level, which is
+ * rarely updated, RCU is used. For the task structures, we use traditional
+ * reference counting. The two techniques are interweaved to achieve overall
+ * consistency.
+ */
+
+static unsigned int duet_poll(struct file *file, poll_table *wait)
+{
+       __u8 *tid = file->private_data;
+       struct duet_task *task;
+       int ret = 0;
+
+       task = duet_find_task(*tid);
+       if (!task) {
+               pr_err("duet_poll: task not found\n");
+               return ret;
+       }
+
+       poll_wait(file, &task->event_queue, wait);
+       if (bitmap_weight(task->bucket_bmap, duet_env.itm_hash_size))
+               ret = POLLIN | POLLRDNORM;
+
+       return ret;
+}
+
+/*
+ * Copy an item to user space, returning how much we copied.
+ *
+ * We already checked that the event size is smaller than the
+ * buffer we had in duet_read() below.
+ */
+static ssize_t copy_item_to_user(struct duet_task *task,
+                                struct duet_item *item,
+                                char __user *buf)
+{
+       size_t item_size = sizeof(struct duet_item);
+
+       /* Send the item */
+       if (copy_to_user(buf, item, item_size))
+               return -EFAULT;
+
+       buf += item_size;
+
+       duet_dbg("duet_read: sending (ino%lu, gen%u, idx%lu, %x)\n",
+                item->uuid.ino, item->uuid.gen, item->idx, item->state);
+
+       return item_size;
+}
+
+/*
+ * Sends out duet items. The number of bytes returned corresponds to the number
+ * of sizeof(struct duet_item) items fetched. Items are checked against the
+ * bitmap, and discarded if they have been marked; this can happen because an
+ * insertion can occur between the last read and the last bitmap set operation.
+ */
+static ssize_t duet_read(struct file *file, char __user *buf,
+                        size_t count, loff_t *pos)
+{
+       struct duet_task *task;
+       struct duet_item item;
+       char __user *start;
+       int ret, err;
+       __u8 *tid;
+       DEFINE_WAIT_FUNC(wait, woken_wake_function);
+
+       start = buf;
+       tid = file->private_data;
+
+       task = duet_find_task(*tid);
+       if (!task)
+               return -ENOENT;
+
+       add_wait_queue(&task->event_queue, &wait);
+       while (1) {
+               /* Fetch an item only if there's space to store it */
+               if (sizeof(struct duet_item) > count)
+                       err = -EINVAL;
+               else
+                       err = hash_fetch(task, &item);
+
+               if (!err) {
+                       ret = copy_item_to_user(task, &item, buf);
+                       if (ret < 0)
+                               break;
+                       buf += ret;
+                       count -= ret;
+                       continue;
+               }
+
+               ret = -EAGAIN;
+               if (file->f_flags & O_NONBLOCK)
+                       break;
+               ret = -ERESTARTSYS;
+               if (signal_pending(current))
+                       break;
+
+               if (start != buf)
+                       break;
+
+               wait_woken(&wait, TASK_INTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
+       }
+       remove_wait_queue(&task->event_queue, &wait);
+
+       if (start != buf && ret != -EFAULT)
+               ret = buf - start;
+
+       /* Decref and wake up cleaner if needed */
+       if (atomic_dec_and_test(&task->refcount))
+               wake_up(&task->cleaner_queue);
+
+       return ret;
+}
+
+/*
+ * Properly dismantle and dispose of a task struct.
+ * At this point we've guaranteed that noone else is accessing the
+ * task struct, so we don't need any locks
+ */
+void duet_task_dispose(struct duet_task *task)
+{
+       int ret = 0;
+       struct duet_item itm;
+
+       /* Dispose of the bitmap tree */
+       bittree_destroy(&task->bittree);
+
+       /* Dispose of hash table entries, bucket bitmap */
+       while (!ret)
+               ret = hash_fetch(task, &itm);
+       kfree(task->bucket_bmap);
+
+       putname(task->name);
+       path_put(task->regpath);
+       kfree(task->regpath);
+       kfree(task->regpathname);
+       kfree(task->pathbuf);
+       kfree(task);
+}
+
+static int duet_release(struct inode *ignored, struct file *file)
+{
+       __u8 *tid = file->private_data;
+       struct duet_task *cur;
+
+       /* Find the task in the list, then dispose of it */
+       mutex_lock(&duet_env.task_list_mutex);
+       list_for_each_entry_rcu(cur, &duet_env.tasks, task_list) {
+               if (cur->id == *tid) {
+#ifdef CONFIG_DUET_STATS
+                       hash_print(cur);
+                       bittree_print(cur);
+#endif /* CONFIG_DUET_STATS */
+                       list_del_rcu(&cur->task_list);
+                       mutex_unlock(&duet_env.task_list_mutex);
+
+                       /* Wait until everyone's done with it */
+                       synchronize_rcu();
+                       wait_event(cur->cleaner_queue,
+                               atomic_read(&cur->refcount) == 0);
+
+                       pr_info("duet: deregistered task %d\n", cur->id);
+
+                       duet_task_dispose(cur);
+                       kfree(tid);
+                       return 0;
+               }
+       }
+       mutex_unlock(&duet_env.task_list_mutex);
+
+       return -ENOENT;
+}
+
+static const struct file_operations duet_fops = {
+       .show_fdinfo    = NULL,
+       .poll           = duet_poll,
+       .read           = duet_read,
+       .fasync         = NULL,
+       .release        = duet_release,
+       .unlocked_ioctl = NULL,
+       .compat_ioctl   = NULL,
+       .llseek         = noop_llseek,
+};
+
+static int process_inode(struct duet_task *task, struct inode *inode)
+{
+       struct radix_tree_iter iter;
+       struct duet_uuid uuid;
+       void **slot;
+       __u16 state;
+
+       /* Use the inode bitmap to decide whether to skip inode */
+       if (bittree_check_inode(&task->bittree, task, inode) == 1)
+               return 0;
+
+       /* Go through all pages of this inode */
+       rcu_read_lock();
+       radix_tree_for_each_slot(slot, &inode->i_mapping->page_tree, &iter, 0) {
+               struct page *page;
+
+               page = radix_tree_deref_slot(slot);
+               if (unlikely(!page))
+                       continue;
+
+               if (radix_tree_exception(page)) {
+                       if (radix_tree_deref_retry(page)) {
+                               slot = radix_tree_iter_retry(&iter);
+                               continue;
+                       }
+                       /*
+                        * Shadow entry of recently evicted page, or swap entry
+                        * from shmem/tmpfs. Skip over it.
+                        */
+                       continue;
+               }
+
+               state = DUET_PAGE_ADDED;
+               if (PageDirty(page))
+                       state |= DUET_PAGE_DIRTY;
+               uuid.ino = inode->i_ino;
+               uuid.gen = inode->i_generation;
+               hash_add(task, uuid, page->index, state, 1);
+       }
+       rcu_read_unlock();
+
+       return 0;
+}
+
+/* Scan through the page cache for events of interest to the task */
+static int scan_page_cache(struct duet_task *task)
+{
+       struct inode *inode, *prev = NULL;
+       struct super_block *sb = task->regpath->mnt->mnt_sb;
+
+       pr_info("duet: page cache scan started\n");
+
+       spin_lock(&sb->s_inode_list_lock);
+       list_for_each_entry(inode, &sb->s_inodes, i_sb_list) {
+               struct address_space *mapping = inode->i_mapping;
+
+               spin_lock(&inode->i_lock);
+               if (inode->i_state & DUET_INODE_FREEING ||
+                   mapping->nrpages == 0) {
+                       spin_unlock(&inode->i_lock);
+                       continue;
+               }
+               atomic_inc(&inode->i_count);
+               spin_unlock(&inode->i_lock);
+               spin_unlock(&sb->s_inode_list_lock);
+
+               /*
+                * We are holding a reference to inode so it won't be removed
+                * from s_inodes list while we don't hold the s_inode_list_lock.
+                * We cannot iput the inode now, though, as we may be holding
+                * the last reference. We will iput it after the iteration is
+                * done.
+                */
+
+               iput(prev);
+               prev = inode;
+
+               process_inode(task, inode);
+
+               spin_lock(&sb->s_inode_list_lock);
+       }
+       spin_unlock(&sb->s_inode_list_lock);
+       iput(prev);
+
+       pr_info("duet: page cache scan finished\n");
+
+       return 0;
+}
+
+/* Find task and increment its refcount */
+struct duet_task *duet_find_task(__u8 id)
+{
+       struct duet_task *cur, *task = NULL;
+
+       rcu_read_lock();
+       list_for_each_entry_rcu(cur, &duet_env.tasks, task_list) {
+               if (cur->id == id) {
+                       task = cur;
+                       atomic_inc(&task->refcount);
+                       break;
+               }
+       }
+       rcu_read_unlock();
+
+       return task;
+}
+
+/* Allocate and initialize a task struct */
+static int duet_task_init(struct duet_task **task, struct filename *name,
+       __u32 regmask, struct path *path)
+{
+       int len;
+       char *p;
+       u32 evtmask = regmask;
+
+       /* Do some sanity checking on event mask. */
+       if (evtmask & DUET_PAGE_EXISTS) {
+               if (evtmask & (DUET_PAGE_ADDED | DUET_PAGE_REMOVED)) {
+                       pr_debug("duet_task_init: invalid regmask\n");
+                       return -EINVAL;
+               }
+               evtmask |= (DUET_PAGE_ADDED | DUET_PAGE_REMOVED);
+       }
+
+       if (evtmask & DUET_PAGE_MODIFIED) {
+               if (evtmask & (DUET_PAGE_DIRTY | DUET_PAGE_FLUSHED)) {
+                       pr_debug("duet_task_init: invalid regmask\n");
+                       goto err;
+               }
+               evtmask |= (DUET_PAGE_DIRTY | DUET_PAGE_FLUSHED);
+       }
+
+       /* Allocate task info struct */
+       *task = kzalloc(sizeof(**task), GFP_KERNEL);
+       if (!(*task))
+               return -ENOMEM;
+
+       /* Allocate temporary space for getpath file paths */
+       (*task)->pathbuf = kzalloc(PATH_MAX, GFP_KERNEL);
+       if (!(*task)->pathbuf) {
+               pr_err("duet_task_init: buffer allocation failed\n");
+               kfree(*task);
+               return -ENOMEM;
+       }
+
+       /* Find and store registered dir path */
+       (*task)->regpathname = kzalloc(PATH_MAX, GFP_KERNEL);
+       if (!(*task)->regpathname) {
+               pr_err("duet_task_init: path allocation failed\n");
+               kfree((*task)->pathbuf);
+               kfree(*task);
+               return -ENOMEM;
+       }
+
+       /* Populate registered dir path buffer */
+       len = PATH_MAX;
+       p = d_path(path, (*task)->pathbuf, len);
+       if (IS_ERR(p)) {
+               pr_err("duet_task_init: path registration failed\n");
+               goto err;
+       } else if (!p) {
+               pr_err("duet_task_init: (null) registered path\n");
+               goto err;
+       }
+
+       (*task)->regpathlen = len - (p - (*task)->pathbuf);
+       memcpy((*task)->regpathname, p, (*task)->regpathlen);
+
+       (*task)->id = 1;
+       (*task)->name = name;
+       (*task)->regpath = path;
+       (*task)->evtmask = (regmask & 0xffff);
+       atomic_set(&(*task)->refcount, 0);
+       INIT_LIST_HEAD(&(*task)->task_list);
+       init_waitqueue_head(&(*task)->cleaner_queue);
+       init_waitqueue_head(&(*task)->event_queue);
+       bittree_init(&(*task)->bittree);
+
+       /* Initialize hash table bitmap */
+       (*task)->bmap_cursor = 0;
+       spin_lock_init(&(*task)->bbmap_lock);
+       (*task)->bucket_bmap = kzalloc(sizeof(unsigned long) *
+               BITS_TO_LONGS(duet_env.itm_hash_size), GFP_KERNEL);
+       if (!(*task)->bucket_bmap) {
+               pr_err("duet_task_init: hash bitmap alloc failed\n");
+               kfree((*task)->regpathname);
+               kfree((*task)->pathbuf);
+               kfree(*task);
+               return -ENOMEM;
+       }
+
+       return 0;
+err:
+       pr_err("duet_task_init: error registering task\n");
+       kfree((*task)->regpathname);
+       kfree((*task)->pathbuf);
+       kfree(*task);
+       return -EINVAL;
+}
+
+/* Register the task with Duet */
+int duet_register_task(struct filename *name, __u32 regmask, struct path *path)
+{
+       int ret;
+       __u8 *tid;
+       struct duet_task *cur, *task = NULL;
+       struct list_head *last;
+
+       /* Do some sanity checking on the parameters passed */
+       if (!path || !regmask)
+               return -EINVAL;
+
+       if (!path->dentry || !path->dentry->d_inode) {
+               pr_err("duet_register_task: invalid path\n");
+               return -EINVAL;
+       }
+
+       if (!S_ISDIR(path->dentry->d_inode->i_mode)) {
+               pr_err("duet_register_task: path is not a dir\n");
+               return -EINVAL;
+       }
+
+       ret = duet_task_init(&task, name, regmask, path);
+       if (ret) {
+               pr_err("duet_register_task: initialization failed\n");
+               return ret;
+       }
+
+       /* Now get an anonymous inode to use for communication with Duet */
+       tid = kzalloc(sizeof(__u8), GFP_KERNEL);
+       if (!tid) {
+               duet_task_dispose(task);
+               return -ENOMEM;
+       }
+
+       ret = anon_inode_getfd("duet", &duet_fops, tid,
+               O_RDONLY | ((regmask & DUET_FD_NONBLOCK) ? O_NONBLOCK : 0));
+       if (ret < 0) {
+               duet_task_dispose(task);
+               kfree(tid);
+               return ret;
+       }
+
+       task->fd = ret;
+
+       /* Find a free task id for the new task. Tasks are sorted by id. */
+       mutex_lock(&duet_env.task_list_mutex);
+       last = &duet_env.tasks;
+       list_for_each_entry_rcu(cur, &duet_env.tasks, task_list) {
+               if (cur->id == task->id)
+                       (task->id)++;
+               else if (cur->id > task->id)
+                       break;
+
+               last = &cur->task_list;
+       }
+       list_add_rcu(&task->task_list, last);
+       mutex_unlock(&duet_env.task_list_mutex);
+
+       *tid = task->id;
+
+       /* Before we return, scan the page cache for pages of interest */
+       scan_page_cache(task);
+
+       pr_info("duet: task %d (fd %d) registered %s(%d) with mask %x\n",
+               task->id, task->fd, task->regpathname, task->regpathlen,
+               task->evtmask);
+
+       return ret;
+}
+
+SYSCALL_DEFINE3(duet_init, const char __user *, taskname, u32, regmask,
+               const char __user *, pathname)
+{
+       int ret;
+       unsigned int lookup_flags = LOOKUP_DIRECTORY;
+       struct filename *name = NULL;
+       struct path *path = NULL;
+
+       if (!capable(CAP_SYS_ADMIN))
+               return -EPERM;
+
+       if (!duet_online())
+               return -ESRCH;
+
+       /* Do some basic sanity checking */
+       if (!pathname || !regmask)
+               return -EINVAL;
+
+       if (taskname) {
+               name = getname(taskname);
+               if (IS_ERR(name))
+                       return PTR_ERR(name);
+       }
+
+       path = kzalloc(sizeof(struct path), GFP_KERNEL);
+       if (!path) {
+               putname(name);
+               return -ENOMEM;
+       }
+
+       ret = user_path_at(AT_FDCWD, pathname, lookup_flags, path);
+       if (ret) {
+               pr_err("duet_init: user_path_at failed\n");
+               goto err;
+       }
+
+       /* Register the task with the framework */
+       ret = duet_register_task(name, regmask, path);
+       if (ret < 0) {
+               pr_err("duet_init: task registration failed\n");
+               goto err;
+       }
+
+       return ret;
+
+err:
+       putname(name);
+       path_put(path);
+       kfree(path);
+       return ret;
+}
+
+SYSCALL_DEFINE2(duet_bmap, u16, flags, struct duet_uuid_arg __user *, arg)
+{
+       int ret = 0;
+       struct duet_uuid_arg *ua;
+       struct duet_task *task;
+
+       if (!capable(CAP_SYS_ADMIN))
+               return -EPERM;
+
+       if (!duet_online())
+               return -ESRCH;
+
+       /* Do some basic sanity checking */
+       if (!arg)
+               return -EINVAL;
+
+       ua = memdup_user(arg, sizeof(*arg));
+       if (IS_ERR(ua))
+               return PTR_ERR(ua);
+
+       /* For now, we only support one struct size */
+       if (ua->size != sizeof(*ua)) {
+               pr_err("duet_bmap: invalid args struct size (%u)\n", ua->size);
+               ret = -EINVAL;
+               goto done;
+       }
+
+       task = duet_find_task(ua->uuid.tid);
+       if (!task)
+               return -ENOENT;
+
+       switch (flags) {
+       case DUET_BMAP_SET:
+               ret = bittree_set(&task->bittree, ua->uuid);
+               break;
+
+       case DUET_BMAP_RESET:
+               ret = bittree_reset(&task->bittree, ua->uuid);
+               break;
+
+       case DUET_BMAP_CHECK:
+               ret = bittree_check(&task->bittree, ua->uuid, task);
+               break;
+
+       default:
+               pr_err("duet_bmap: invalid flags\n");
+               ret = -EINVAL;
+               break;
+       }
+
+       /* decreg and wake up cleaner if needed */
+       if (atomic_dec_and_test(&task->refcount))
+               wake_up(&task->cleaner_queue);
+
+done:
+       kfree(ua);
+       return ret;
+}
-- 
2.7.4

Reply via email to