jiffy range covered, and the SECS field the equivalent number of seconds.
 
 
+===========
+OBJECT LIST
+===========
+
+If CONFIG_FSCACHE_OBJECT_LIST is enabled, the FS-Cache facility will maintain a
+list of all the objects currently allocated and allow them to be viewed
+through:
+
+       /proc/fs/fscache/objects
+
+This will look something like:
+
+       [root@andromeda ~]# head /proc/fs/fscache/objects
+       OBJECT   PARENT   STAT CHLDN OPS OOP IPR EX READS EM EV F S | NETFS_COOKIE_DEF TY FL NETFS_DATA       OBJECT_KEY, AUX_DATA
+       ======== ======== ==== ===== === === === == ===== == == = = | ================ == == ================ ================
+          17e4b        2 ACTV     0   0   0   0  0     0 7b  4 0 8 | NFS.fh           DT  0 ffff88001dd82820 010006017edcf8bbc93b43298fdfbe71e50b57b13a172c0117f38472, e567634700000000000000000000000063f2404a000000000000000000000000c9030000000000000000000063f2404a
+          1693a        2 ACTV     0   0   0   0  0     0 7b  4 0 8 | NFS.fh           DT  0 ffff88002db23380 010006017edcf8bbc93b43298fdfbe71e50b57b1e0162c01a2df0ea6, 420ebc4a000000000000000000000000420ebc4a0000000000000000000000000e1801000000000000000000420ebc4a
+
+where the first set of columns before the '|' describe the object:
+
+       COLUMN  DESCRIPTION
+       ======= ===============================================================
+       OBJECT  Object debugging ID (appears as OBJ%x in some debug messages)
+       PARENT  Debugging ID of parent object
+       STAT    Object state
+       CHLDN   Number of child objects of this object
+       OPS     Number of outstanding operations on this object
+       OOP     Number of outstanding child object management operations
+       IPR
+       EX      Number of outstanding exclusive operations
+       READS   Number of outstanding read operations
+       EM      Object's event mask
+       EV      Events raised on this object
+       F       Object flags
+       S       Object slow-work work item flags
+
+and the second set of columns describe the object's cookie, if present:
+
+       COLUMN          DESCRIPTION
+       =============== =======================================================
+       NETFS_COOKIE_DEF Name of netfs cookie definition
+       TY              Cookie type (IX - index, DT - data, hex - special)
+       FL              Cookie flags
+       NETFS_DATA      Netfs private data stored in the cookie
+       OBJECT_KEY      Object key      } 1 column, with separating comma
+       AUX_DATA        Object aux data } presence may be configured
+
+The data shown may be filtered by attaching the a key to an appropriate keyring
+before viewing the file.  Something like:
+
+               keyctl add user fscache:objlist <restrictions> @s
+
+where <restrictions> are a selection of the following letters:
+
+       K       Show hexdump of object key (don't show if not given)
+       A       Show hexdump of object aux data (don't show if not given)
+
+and the following paired letters:
+
+       C       Show objects that have a cookie
+       c       Show objects that don't have a cookie
+       B       Show objects that are busy
+       b       Show objects that aren't busy
+       W       Show objects that have pending writes
+       w       Show objects that don't have pending writes
+       R       Show objects that have outstanding reads
+       r       Show objects that don't have outstanding reads
+       S       Show objects that have slow work queued
+       s       Show objects that don't have slow work queued
+
+If neither side of a letter pair is given, then both are implied.  For example:
+
+       keyctl add user fscache:objlist KB @s
+
+shows objects that are busy, and lists their object keys, but does not dump
+their auxiliary data.  It also implies "CcWwRrSs", but as 'B' is given, 'b' is
+not implied.
+
+By default all objects and all fields will be shown.
+
+
 =========
 DEBUGGING
 =========
 
--- /dev/null
+/* Global fscache object list maintainer and viewer
+ *
+ * Copyright (C) 2009 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#define FSCACHE_DEBUG_LEVEL COOKIE
+#include <linux/module.h>
+#include <linux/seq_file.h>
+#include <linux/key.h>
+#include <keys/user-type.h>
+#include "internal.h"
+
+static struct rb_root fscache_object_list;
+static DEFINE_RWLOCK(fscache_object_list_lock);
+
+struct fscache_objlist_data {
+       unsigned long   config;         /* display configuration */
+#define FSCACHE_OBJLIST_CONFIG_KEY     0x00000001      /* show object keys */
+#define FSCACHE_OBJLIST_CONFIG_AUX     0x00000002      /* show object auxdata */
+#define FSCACHE_OBJLIST_CONFIG_COOKIE  0x00000004      /* show objects with cookies */
+#define FSCACHE_OBJLIST_CONFIG_NOCOOKIE        0x00000008      /* show objects without cookies */
+#define FSCACHE_OBJLIST_CONFIG_BUSY    0x00000010      /* show busy objects */
+#define FSCACHE_OBJLIST_CONFIG_IDLE    0x00000020      /* show idle objects */
+#define FSCACHE_OBJLIST_CONFIG_PENDWR  0x00000040      /* show objects with pending writes */
+#define FSCACHE_OBJLIST_CONFIG_NOPENDWR        0x00000080      /* show objects without pending writes */
+#define FSCACHE_OBJLIST_CONFIG_READS   0x00000100      /* show objects with active reads */
+#define FSCACHE_OBJLIST_CONFIG_NOREADS 0x00000200      /* show objects without active reads */
+#define FSCACHE_OBJLIST_CONFIG_EVENTS  0x00000400      /* show objects with events */
+#define FSCACHE_OBJLIST_CONFIG_NOEVENTS        0x00000800      /* show objects without no events */
+#define FSCACHE_OBJLIST_CONFIG_WORK    0x00001000      /* show objects with slow work */
+#define FSCACHE_OBJLIST_CONFIG_NOWORK  0x00002000      /* show objects without slow work */
+
+       u8              buf[512];       /* key and aux data buffer */
+};
+
+/*
+ * Add an object to the object list
+ * - we use the address of the fscache_object structure as the key into the
+ *   tree
+ */
+void fscache_objlist_add(struct fscache_object *obj)
+{
+       struct fscache_object *xobj;
+       struct rb_node **p = &fscache_object_list.rb_node, *parent = NULL;
+
+       write_lock(&fscache_object_list_lock);
+
+       while (*p) {
+               parent = *p;
+               xobj = rb_entry(parent, struct fscache_object, objlist_link);
+
+               if (obj < xobj)
+                       p = &(*p)->rb_left;
+               else if (obj > xobj)
+                       p = &(*p)->rb_right;
+               else
+                       BUG();
+       }
+
+       rb_link_node(&obj->objlist_link, parent, p);
+       rb_insert_color(&obj->objlist_link, &fscache_object_list);
+
+       write_unlock(&fscache_object_list_lock);
+}
+
+/**
+ * fscache_object_destroy - Note that a cache object is about to be destroyed
+ * @object: The object to be destroyed
+ *
+ * Note the imminent destruction and deallocation of a cache object record.
+ */
+void fscache_object_destroy(struct fscache_object *obj)
+{
+       write_lock(&fscache_object_list_lock);
+
+       BUG_ON(RB_EMPTY_ROOT(&fscache_object_list));
+       rb_erase(&obj->objlist_link, &fscache_object_list);
+
+       write_unlock(&fscache_object_list_lock);
+}
+EXPORT_SYMBOL(fscache_object_destroy);
+
+/*
+ * find the object in the tree on or after the specified index
+ */
+static struct fscache_object *fscache_objlist_lookup(loff_t *_pos)
+{
+       struct fscache_object *pobj, *obj, *minobj = NULL;
+       struct rb_node *p;
+       unsigned long pos;
+
+       if (*_pos >= (unsigned long) ERR_PTR(-ENOENT))
+               return NULL;
+       pos = *_pos;
+
+       /* banners (can't represent line 0 by pos 0 as that would involve
+        * returning a NULL pointer) */
+       if (pos == 0)
+               return (struct fscache_object *) ++(*_pos);
+       if (pos < 3)
+               return (struct fscache_object *)pos;
+
+       pobj = (struct fscache_object *)pos;
+       p = fscache_object_list.rb_node;
+       while (p) {
+               obj = rb_entry(p, struct fscache_object, objlist_link);
+               if (pobj < obj) {
+                       if (!minobj || minobj > obj)
+                               minobj = obj;
+                       p = p->rb_left;
+               } else if (pobj > obj) {
+                       p = p->rb_right;
+               } else {
+                       minobj = obj;
+                       break;
+               }
+               obj = NULL;
+       }
+
+       if (!minobj)
+               *_pos = (unsigned long) ERR_PTR(-ENOENT);
+       else if (minobj != obj)
+               *_pos = (unsigned long) minobj;
+       return minobj;
+}
+
+/*
+ * set up the iterator to start reading from the first line
+ */
+static void *fscache_objlist_start(struct seq_file *m, loff_t *_pos)
+       __acquires(&fscache_object_list_lock)
+{
+       read_lock(&fscache_object_list_lock);
+       return fscache_objlist_lookup(_pos);
+}
+
+/*
+ * move to the next line
+ */
+static void *fscache_objlist_next(struct seq_file *m, void *v, loff_t *_pos)
+{
+       (*_pos)++;
+       return fscache_objlist_lookup(_pos);
+}
+
+/*
+ * clean up after reading
+ */
+static void fscache_objlist_stop(struct seq_file *m, void *v)
+       __releases(&fscache_object_list_lock)
+{
+       read_unlock(&fscache_object_list_lock);
+}
+
+/*
+ * display an object
+ */
+static int fscache_objlist_show(struct seq_file *m, void *v)
+{
+       struct fscache_objlist_data *data = m->private;
+       struct fscache_object *obj = v;
+       unsigned long config = data->config;
+       uint16_t keylen, auxlen;
+       char _type[3], *type;
+       bool no_cookie;
+       u8 *buf = data->buf, *p;
+
+       if ((unsigned long) v == 1) {
+               seq_puts(m, "OBJECT   PARENT   STAT CHLDN OPS OOP IPR EX READS"
+                        " EM EV F S"
+                        " | NETFS_COOKIE_DEF TY FL NETFS_DATA");
+               if (config & (FSCACHE_OBJLIST_CONFIG_KEY |
+                             FSCACHE_OBJLIST_CONFIG_AUX))
+                       seq_puts(m, "       ");
+               if (config & FSCACHE_OBJLIST_CONFIG_KEY)
+                       seq_puts(m, "OBJECT_KEY");
+               if ((config & (FSCACHE_OBJLIST_CONFIG_KEY |
+                              FSCACHE_OBJLIST_CONFIG_AUX)) ==
+                   (FSCACHE_OBJLIST_CONFIG_KEY | FSCACHE_OBJLIST_CONFIG_AUX))
+                       seq_puts(m, ", ");
+               if (config & FSCACHE_OBJLIST_CONFIG_AUX)
+                       seq_puts(m, "AUX_DATA");
+               seq_puts(m, "\n");
+               return 0;
+       }
+
+       if ((unsigned long) v == 2) {
+               seq_puts(m, "======== ======== ==== ===== === === === == ====="
+                        " == == = ="
+                        " | ================ == == ================");
+               if (config & (FSCACHE_OBJLIST_CONFIG_KEY |
+                             FSCACHE_OBJLIST_CONFIG_AUX))
+                       seq_puts(m, " ================");
+               seq_puts(m, "\n");
+               return 0;
+       }
+
+       /* filter out any unwanted objects */
+#define FILTER(criterion, _yes, _no)                                   \
+       do {                                                            \
+               unsigned long yes = FSCACHE_OBJLIST_CONFIG_##_yes;      \
+               unsigned long no = FSCACHE_OBJLIST_CONFIG_##_no;        \
+               if (criterion) {                                        \
+                       if (!(config & yes))                            \
+                               return 0;                               \
+               } else {                                                \
+                       if (!(config & no))                             \
+                               return 0;                               \
+               }                                                       \
+       } while(0)
+
+       if (~config) {
+               FILTER(obj->cookie,
+                      COOKIE, NOCOOKIE);
+               FILTER(obj->state != FSCACHE_OBJECT_ACTIVE ||
+                      obj->n_ops != 0 ||
+                      obj->n_obj_ops != 0 ||
+                      obj->flags ||
+                      !list_empty(&obj->dependents),
+                      BUSY, IDLE);
+               FILTER(test_bit(FSCACHE_OBJECT_PENDING_WRITE, &obj->flags),
+                      PENDWR, NOPENDWR);
+               FILTER(atomic_read(&obj->n_reads),
+                      READS, NOREADS);
+               FILTER(obj->events & obj->event_mask,
+                      EVENTS, NOEVENTS);
+               FILTER(obj->work.flags & ~(1UL << SLOW_WORK_VERY_SLOW),
+                      WORK, NOWORK);
+       }
+
+       seq_printf(m,
+                  "%8x %8x %s %5u %3u %3u %3u %2u %5u %2lx %2lx %1lx %1lx | ",
+                  obj->debug_id,
+                  obj->parent ? obj->parent->debug_id : -1,
+                  fscache_object_states_short[obj->state],
+                  obj->n_children,
+                  obj->n_ops,
+                  obj->n_obj_ops,
+                  obj->n_in_progress,
+                  obj->n_exclusive,
+                  atomic_read(&obj->n_reads),
+                  obj->event_mask & FSCACHE_OBJECT_EVENTS_MASK,
+                  obj->events,
+                  obj->flags,
+                  obj->work.flags);
+
+       no_cookie = true;
+       keylen = auxlen = 0;
+       if (obj->cookie) {
+               spin_lock(&obj->lock);
+               if (obj->cookie) {
+                       switch (obj->cookie->def->type) {
+                       case 0:
+                               type = "IX";
+                               break;
+                       case 1:
+                               type = "DT";
+                               break;
+                       default:
+                               sprintf(_type, "%02u",
+                                       obj->cookie->def->type);
+                               type = _type;
+                               break;
+                       }
+
+                       seq_printf(m, "%-16s %s %2lx %16p",
+                                  obj->cookie->def->name,
+                                  type,
+                                  obj->cookie->flags,
+                                  obj->cookie->netfs_data);
+
+                       if (obj->cookie->def->get_key &&
+                           config & FSCACHE_OBJLIST_CONFIG_KEY)
+                               keylen = obj->cookie->def->get_key(
+                                       obj->cookie->netfs_data,
+                                       buf, 400);
+
+                       if (obj->cookie->def->get_aux &&
+                           config & FSCACHE_OBJLIST_CONFIG_AUX)
+                               auxlen = obj->cookie->def->get_aux(
+                                       obj->cookie->netfs_data,
+                                       buf + keylen, 512 - keylen);
+
+                       no_cookie = false;
+               }
+               spin_unlock(&obj->lock);
+
+               if (!no_cookie && (keylen > 0 || auxlen > 0)) {
+                       seq_printf(m, " ");
+                       for (p = buf; keylen > 0; keylen--)
+                               seq_printf(m, "%02x", *p++);
+                       if (auxlen > 0) {
+                               if (config & FSCACHE_OBJLIST_CONFIG_KEY)
+                                       seq_printf(m, ", ");
+                               for (; auxlen > 0; auxlen--)
+                                       seq_printf(m, "%02x", *p++);
+                       }
+               }
+       }
+
+       if (no_cookie)
+               seq_printf(m, "<no_cookie>\n");
+       else
+               seq_printf(m, "\n");
+       return 0;
+}
+
+static const struct seq_operations fscache_objlist_ops = {
+       .start          = fscache_objlist_start,
+       .stop           = fscache_objlist_stop,
+       .next           = fscache_objlist_next,
+       .show           = fscache_objlist_show,
+};
+
+/*
+ * get the configuration for filtering the list
+ */
+static void fscache_objlist_config(struct fscache_objlist_data *data)
+{
+#ifdef CONFIG_KEYS
+       struct user_key_payload *confkey;
+       unsigned long config;
+       struct key *key;
+       const char *buf;
+       int len;
+
+       key = request_key(&key_type_user, "fscache:objlist", NULL);
+       if (IS_ERR(key))
+               goto no_config;
+
+       config = 0;
+       rcu_read_lock();
+
+       confkey = key->payload.data;
+       buf = confkey->data;
+
+       for (len = confkey->datalen - 1; len >= 0; len--) {
+               switch (buf[len]) {
+               case 'K': config |= FSCACHE_OBJLIST_CONFIG_KEY;         break;
+               case 'A': config |= FSCACHE_OBJLIST_CONFIG_AUX;         break;
+               case 'C': config |= FSCACHE_OBJLIST_CONFIG_COOKIE;      break;
+               case 'c': config |= FSCACHE_OBJLIST_CONFIG_NOCOOKIE;    break;
+               case 'B': config |= FSCACHE_OBJLIST_CONFIG_BUSY;        break;
+               case 'b': config |= FSCACHE_OBJLIST_CONFIG_IDLE;        break;
+               case 'W': config |= FSCACHE_OBJLIST_CONFIG_PENDWR;      break;
+               case 'w': config |= FSCACHE_OBJLIST_CONFIG_NOPENDWR;    break;
+               case 'R': config |= FSCACHE_OBJLIST_CONFIG_READS;       break;
+               case 'r': config |= FSCACHE_OBJLIST_CONFIG_NOREADS;     break;
+               case 'S': config |= FSCACHE_OBJLIST_CONFIG_WORK;        break;
+               case 's': config |= FSCACHE_OBJLIST_CONFIG_NOWORK;      break;
+               }
+       }
+
+       rcu_read_unlock();
+       key_put(key);
+
+       if (!(config & (FSCACHE_OBJLIST_CONFIG_COOKIE | FSCACHE_OBJLIST_CONFIG_NOCOOKIE)))
+           config   |= FSCACHE_OBJLIST_CONFIG_COOKIE | FSCACHE_OBJLIST_CONFIG_NOCOOKIE;
+       if (!(config & (FSCACHE_OBJLIST_CONFIG_BUSY | FSCACHE_OBJLIST_CONFIG_IDLE)))
+           config   |= FSCACHE_OBJLIST_CONFIG_BUSY | FSCACHE_OBJLIST_CONFIG_IDLE;
+       if (!(config & (FSCACHE_OBJLIST_CONFIG_PENDWR | FSCACHE_OBJLIST_CONFIG_NOPENDWR)))
+           config   |= FSCACHE_OBJLIST_CONFIG_PENDWR | FSCACHE_OBJLIST_CONFIG_NOPENDWR;
+       if (!(config & (FSCACHE_OBJLIST_CONFIG_READS | FSCACHE_OBJLIST_CONFIG_NOREADS)))
+           config   |= FSCACHE_OBJLIST_CONFIG_READS | FSCACHE_OBJLIST_CONFIG_NOREADS;
+       if (!(config & (FSCACHE_OBJLIST_CONFIG_EVENTS | FSCACHE_OBJLIST_CONFIG_NOEVENTS)))
+           config   |= FSCACHE_OBJLIST_CONFIG_EVENTS | FSCACHE_OBJLIST_CONFIG_NOEVENTS;
+       if (!(config & (FSCACHE_OBJLIST_CONFIG_WORK | FSCACHE_OBJLIST_CONFIG_NOWORK)))
+           config   |= FSCACHE_OBJLIST_CONFIG_WORK | FSCACHE_OBJLIST_CONFIG_NOWORK;
+
+       data->config = config;
+       return;
+
+no_config:
+#endif
+       data->config = ULONG_MAX;
+}
+
+/*
+ * open "/proc/fs/fscache/objects" to provide a list of active objects
+ * - can be configured by a user-defined key added to the caller's keyrings
+ */
+static int fscache_objlist_open(struct inode *inode, struct file *file)
+{
+       struct fscache_objlist_data *data;
+       struct seq_file *m;
+       int ret;
+
+       ret = seq_open(file, &fscache_objlist_ops);
+       if (ret < 0)
+               return ret;
+
+       m = file->private_data;
+
+       /* buffer for key extraction */
+       data = kmalloc(sizeof(struct fscache_objlist_data), GFP_KERNEL);
+       if (!data) {
+               seq_release(inode, file);
+               return -ENOMEM;
+       }
+
+       /* get the configuration key */
+       fscache_objlist_config(data);
+
+       m->private = data;
+       return 0;
+}
+
+/*
+ * clean up on close
+ */
+static int fscache_objlist_release(struct inode *inode, struct file *file)
+{
+       struct seq_file *m = file->private_data;
+
+       kfree(m->private);
+       m->private = NULL;
+       return seq_release(inode, file);
+}
+
+const struct file_operations fscache_objlist_fops = {
+       .owner          = THIS_MODULE,
+       .open           = fscache_objlist_open,
+       .read           = seq_read,
+       .llseek         = seq_lseek,
+       .release        = fscache_objlist_release,
+};