summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/rpc-cache.txt32
-rw-r--r--include/linux/sunrpc/cache.h11
-rw-r--r--net/sunrpc/cache.c200
3 files changed, 228 insertions, 15 deletions
diff --git a/Documentation/rpc-cache.txt b/Documentation/rpc-cache.txt
index ccfe5587f81b..1f999b9f2987 100644
--- a/Documentation/rpc-cache.txt
+++ b/Documentation/rpc-cache.txt
@@ -138,4 +138,34 @@ Each cache should define a "cache_parse" method which takes a message
written from user-space and processes it. It should return an error
(which propagates back to the write syscall) or 0.
-
+Each cache should also define a "cache_request" method which
+takes a cache item and encodes a request into the buffer
+provided.
+
+
+Note: If a cache has no active readers on the channel, and has had not
+active readers for more than 60 seconds, further requests will not be
+added to the channel but instead all looks that do not find a valid
+entry will fail. This is partly for backward compatability: The
+previous nfs exports table was deemed to be authoritative and a
+failed lookup meant a definate 'no'.
+
+request/response format
+-----------------------
+
+While each cache is free to use it's own format for requests
+and responses over channel, the following is recommended are
+appropriate and support routines are available to help:
+Each request or response record should be printable ASCII
+with precisely one newline character which should be at the end.
+Fields within the record should be separated by spaces, normally one.
+If spaces, newlines, or nul characters are needed in a field they
+much be quotes. two mechanisms are available:
+1/ If a field begins '\x' then it must contain an even number of
+ hex digits, and pairs of these digits provide the bytes in the
+ field.
+2/ otherwise a \ in the field must be followed by 3 octal digits
+ which give the code for a byte. Other characters are treated
+ as them selves. At the very least, space, newlines nul, and
+ '\' must be quoted in this way.
+
diff --git a/include/linux/sunrpc/cache.h b/include/linux/sunrpc/cache.h
index c501845cac0a..7d9f0bc0f906 100644
--- a/include/linux/sunrpc/cache.h
+++ b/include/linux/sunrpc/cache.h
@@ -72,9 +72,9 @@ struct cache_detail {
void (*cache_put)(struct cache_head *,
struct cache_detail*);
- /* request and update functions for interaction with userspace
- * will go here
- */
+ void (*cache_request)(struct cache_detail *cd,
+ struct cache_head *h,
+ char **bpp, int *blen);
int (*cache_parse)(struct cache_detail *,
char *buf, int len);
@@ -90,6 +90,8 @@ struct cache_detail {
/* fields for communication over channel */
struct list_head queue;
struct proc_dir_entry *proc_ent;
+ atomic_t readers; /* how many time is /chennel open */
+ time_t last_close; /* it no readers, when did last close */
};
@@ -269,4 +271,7 @@ extern int cache_unregister(struct cache_detail *cd);
extern struct cache_detail *cache_find(char *name);
extern void cache_drop(struct cache_detail *detail);
+extern void add_word(char **bpp, int *lp, char *str);
+extern void add_hex(char **bpp, int *lp, char *buf, int blen);
+
#endif /* _LINUX_SUNRPC_CACHE_H_ */
diff --git a/net/sunrpc/cache.c b/net/sunrpc/cache.c
index 0ec038f86c8f..ec6b28cbc4de 100644
--- a/net/sunrpc/cache.c
+++ b/net/sunrpc/cache.c
@@ -40,6 +40,7 @@ void cache_init(struct cache_head *h)
}
+static int cache_make_upcall(struct cache_detail *detail, struct cache_head *h);
/*
* This is the generic cache management routine for all
* the authentication caches.
@@ -55,6 +56,7 @@ int cache_check(struct cache_detail *detail,
struct cache_head *h, struct cache_req *rqstp)
{
int rv;
+ long refresh_age, age;
/* First decide return status as best we can */
if (!test_bit(CACHE_VALID, &h->flags) ||
@@ -69,31 +71,54 @@ int cache_check(struct cache_detail *detail,
else rv = 0;
}
- /* up-call processing goes here later */
- /* if cache_pending, initiate upcall if none pending.
- * if upcall cannot be initiated, change to CACHE_NEGATIVE
- */
- if (rv == CACHE_PENDING) rv = CACHE_NEGATIVE;
+ /* now see if we want to start an upcall */
+ refresh_age = (h->expiry_time - h->last_refresh);
+ age = CURRENT_TIME - h->last_refresh;
- if (rv == CACHE_PENDING)
- cache_defer_req(rqstp, h);
+ if (rqstp == NULL) {
+ if (rv == -EAGAIN)
+ rv = -ENOENT;
+ } else if (rv == -EAGAIN || age > refresh_age/2) {
+ dprintk("Want update, refage=%ld, age=%ld\n", refresh_age, age);
+ if (!test_and_set_bit(CACHE_PENDING, &h->flags)) {
+ switch (cache_make_upcall(detail, h)) {
+ case -EINVAL:
+ clear_bit(CACHE_PENDING, &h->flags);
+ if (rv == -EAGAIN) {
+ set_bit(CACHE_NEGATIVE, &h->flags);
+ cache_fresh(detail, h, CURRENT_TIME+CACHE_NEW_EXPIRY);
+ rv = -ENOENT;
+ }
+ break;
- if (rv == -EAGAIN /* && cannot do upcall */)
- rv = -ENOENT;
+ case -EAGAIN:
+ clear_bit(CACHE_PENDING, &h->flags);
+ cache_revisit_request(h);
+ break;
+ }
+ }
+ }
+
+ if (rv == -EAGAIN)
+ cache_defer_req(rqstp, h);
if (rv && h)
detail->cache_put(h, detail);
return rv;
}
+static void queue_loose(struct cache_detail *detail, struct cache_head *ch);
+
void cache_fresh(struct cache_detail *detail,
struct cache_head *head, time_t expiry)
{
head->expiry_time = expiry;
head->last_refresh = CURRENT_TIME;
- set_bit(CACHE_VALID, &head->flags);
- clear_bit(CACHE_PENDING, &head->flags);
+ if (!test_and_set_bit(CACHE_VALID, &head->flags))
+ cache_revisit_request(head);
+ if (test_and_clear_bit(CACHE_PENDING, &head->flags))
+ queue_loose(detail, head);
}
/*
@@ -155,6 +180,8 @@ void cache_register(struct cache_detail *cd)
spin_lock(&cache_list_lock);
cd->nextcheck = 0;
cd->entries = 0;
+ atomic_set(&cd->readers, 0);
+ cd->last_close = CURRENT_TIME;
list_add(&cd->others, &cache_list);
spin_unlock(&cache_list_lock);
}
@@ -639,6 +666,7 @@ cache_open(struct inode *inode, struct file *filp)
rp->page = NULL;
rp->offset = 0;
rp->q.reader = 1;
+ atomic_inc(&cd->readers);
spin_lock(&queue_lock);
list_add(&rp->q.list, &cd->queue);
spin_unlock(&queue_lock);
@@ -672,6 +700,9 @@ cache_release(struct inode *inode, struct file *filp)
filp->private_data = NULL;
kfree(rp);
+
+ cd->last_close = CURRENT_TIME;
+ atomic_dec(&cd->readers);
return 0;
}
@@ -686,3 +717,150 @@ struct file_operations cache_file_operations = {
.open = cache_open,
.release = cache_release,
};
+
+
+static void queue_loose(struct cache_detail *detail, struct cache_head *ch)
+{
+ struct cache_queue *cq;
+ spin_lock(&queue_lock);
+ list_for_each_entry(cq, &detail->queue, list)
+ if (!cq->reader) {
+ struct cache_request *cr = container_of(cq, struct cache_request, q);
+ if (cr->item != ch)
+ continue;
+ if (cr->readers != 0)
+ break;
+ list_del(&cr->q.list);
+ spin_unlock(&queue_lock);
+ detail->cache_put(cr->item, detail);
+ kfree(cr->buf);
+ kfree(cr);
+ return;
+ }
+ spin_unlock(&queue_lock);
+}
+
+/*
+ * Support routines for text-based upcalls.
+ * Fields are separated by spaces.
+ * Fields are either mangled to quote space tab newline slosh with slosh
+ * or a hexified with a leading \x
+ * Record is terminated with newline.
+ *
+ */
+
+void add_word(char **bpp, int *lp, char *str)
+{
+ char *bp = *bpp;
+ int len = *lp;
+ char c;
+
+ if (len < 0) return;
+
+ while ((c=*str++) && len)
+ switch(c) {
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\\':
+ if (len >= 4) {
+ *bp++ = '\\';
+ *bp++ = '0' + ((c & 0300)>>6);
+ *bp++ = '0' + ((c & 0070)>>3);
+ *bp++ = '0' + ((c & 0007)>>0);
+ }
+ len -= 4;
+ break;
+ default:
+ *bp++ = c;
+ len--;
+ }
+ if (c || len <1) len = -1;
+ else {
+ *bp++ = ' ';
+ len--;
+ }
+ *bpp = bp;
+ *lp = len;
+}
+
+void add_hex(char **bpp, int *lp, char *buf, int blen)
+{
+ char *bp = *bpp;
+ int len = *lp;
+
+ if (len < 0) return;
+
+ if (len > 2) {
+ *bp++ = '\\';
+ *bp++ = 'x';
+ len -= 2;
+ while (blen && len >= 2) {
+ unsigned char c = *buf++;
+ *bp++ = '0' + ((c&0xf0)>>4) + (c>=0xa0)*('a'-'0');
+ *bp++ = '0' + (c&0x0f) + ((c&0x0f)>=0x0a)*('a'-'0');
+ len -= 2;
+ blen--;
+ }
+ }
+ if (blen || len<1) len = -1;
+ else {
+ *bp++ = ' ';
+ len--;
+ }
+ *bpp = bp;
+ *lp = len;
+}
+
+
+
+/*
+ * register an upcall request to user-space.
+ * Each request is at most one page long.
+ */
+static int cache_make_upcall(struct cache_detail *detail, struct cache_head *h)
+{
+
+ char *buf;
+ struct cache_request *crq;
+ char *bp;
+ int len;
+
+ if (detail->cache_request == NULL)
+ return -EINVAL;
+
+ if (atomic_read(&detail->readers) == 0 &&
+ detail->last_close < CURRENT_TIME - 60)
+ /* nobody is listening */
+ return -EINVAL;
+
+ buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
+ if (!buf)
+ return -EAGAIN;
+
+ crq = kmalloc(sizeof (*crq), GFP_KERNEL);
+ if (!crq) {
+ kfree(buf);
+ return -EAGAIN;
+ }
+
+ bp = buf; len = PAGE_SIZE;
+
+ detail->cache_request(detail, h, &bp, &len);
+
+ if (len < 0) {
+ kfree(buf);
+ kfree(crq);
+ return -EAGAIN;
+ }
+ crq->q.reader = 0;
+ crq->item = cache_get(h);
+ crq->buf = buf;
+ crq->len = PAGE_SIZE - len;
+ crq->readers = 0;
+ spin_lock(&queue_lock);
+ list_add_tail(&crq->q.list, &detail->queue);
+ spin_unlock(&queue_lock);
+ wake_up(&queue_wait);
+ return 0;
+}