This commit was manufactured by cvs2svn to create branch 'vserver'.
[linux-2.6.git] / fs / cachefiles / cf-key.c
diff --git a/fs/cachefiles/cf-key.c b/fs/cachefiles/cf-key.c
new file mode 100644 (file)
index 0000000..cfae5a7
--- /dev/null
@@ -0,0 +1,157 @@
+/* cf-key.c: Key to pathname encoder
+ *
+ * Copyright (C) 2006 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 License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/slab.h>
+#include "internal.h"
+
+static const char cachefiles_charmap[64] =
+       "0123456789"                    /* 0 - 9 */
+       "abcdefghijklmnopqrstuvwxyz"    /* 10 - 35 */
+       "ABCDEFGHIJKLMNOPQRSTUVWXYZ"    /* 36 - 61 */
+       "_-"                            /* 62 - 63 */
+       ;
+
+static const char cachefiles_filecharmap[256] = {
+       /* we skip space and tab and control chars */
+       [ 33 ... 46 ] = 1,              /* '!' -> '.' */
+       /* we skip '/' as it's significant to pathwalk */
+       [ 48 ... 127 ] = 1,             /* '0' -> '~' */
+};
+
+/*****************************************************************************/
+/*
+ * turn the raw key into something cooked
+ * - the raw key should include the length in the two bytes at the front
+ * - the key may be up to 514 bytes in length (including the length word)
+ *   - "base64" encode the strange keys, mapping 3 bytes of raw to four of
+ *     cooked
+ *   - need to cut the cooked key into 252 char lengths (189 raw bytes)
+ */
+char *cachefiles_cook_key(const u8 *raw, int keylen, uint8_t type)
+{
+       unsigned char csum, ch;
+       unsigned int acc;
+       char *key;
+       int loop, len, max, seg, mark, print;
+
+       _enter(",%d", keylen);
+
+       BUG_ON(keylen < 2 || keylen > 514);
+
+       csum = raw[0] + raw[1];
+       print = 1;
+       for (loop = 2; loop < keylen; loop++) {
+               ch = raw[loop];
+               csum += ch;
+               print &= cachefiles_filecharmap[ch];
+       }
+
+       if (print) {
+               /* if the path is usable ASCII, then we render it directly */
+               max = keylen - 2;
+               max += 2;       /* two base64'd length chars on the front */
+               max += 5;       /* @checksum/M */
+               max += 3 * 2;   /* maximum number of segment dividers (".../M")
+                                * is ((514 + 251) / 252) = 3
+                                */
+               max += 1;       /* NUL on end */
+       } else {
+               /* calculate the maximum length of the cooked key */
+               keylen = (keylen + 2) / 3;
+
+               max = keylen * 4;
+               max += 5;       /* @checksum/M */
+               max += 3 * 2;   /* maximum number of segment dividers (".../M")
+                                * is ((514 + 188) / 189) = 3
+                                */
+               max += 1;       /* NUL on end */
+       }
+
+       _debug("max: %d", max);
+
+       key = kmalloc(max, GFP_KERNEL);
+       if (!key)
+               return NULL;
+
+       len = 0;
+
+       /* build the cooked key */
+       sprintf(key, "@%02x/+", (unsigned) csum);
+       len = 5;
+       mark = len - 1;
+
+       if (print) {
+               acc = *(uint16_t *) raw;
+               raw += 2;
+
+               key[len + 1] = cachefiles_charmap[acc & 63];
+               acc >>= 6;
+               key[len] = cachefiles_charmap[acc & 63];
+               len += 2;
+
+               seg = 250;
+               for (loop = keylen; loop > 0; loop--) {
+                       if (seg <= 0) {
+                               key[len++] = '/';
+                               mark = len;
+                               key[len++] = '+';
+                               seg = 252;
+                       }
+
+                       key[len++] = *raw++;
+                       ASSERT(len < max);
+               }
+
+               switch (type) {
+               case FSCACHE_COOKIE_TYPE_INDEX:         type = 'I';     break;
+               case FSCACHE_COOKIE_TYPE_DATAFILE:      type = 'D';     break;
+               default:                                type = 'S';     break;
+               }
+       } else {
+               seg = 252;
+               for (loop = keylen; loop > 0; loop--) {
+                       if (seg <= 0) {
+                               key[len++] = '/';
+                               mark = len;
+                               key[len++] = '+';
+                               seg = 252;
+                       }
+
+                       acc = *raw++;
+                       acc |= *raw++ << 8;
+                       acc |= *raw++ << 16;
+
+                       _debug("acc: %06x", acc);
+
+                       key[len++] = cachefiles_charmap[acc & 63];
+                       acc >>= 6;
+                       key[len++] = cachefiles_charmap[acc & 63];
+                       acc >>= 6;
+                       key[len++] = cachefiles_charmap[acc & 63];
+                       acc >>= 6;
+                       key[len++] = cachefiles_charmap[acc & 63];
+
+                       ASSERT(len < max);
+               }
+
+               switch (type) {
+               case FSCACHE_COOKIE_TYPE_INDEX:         type = 'J';     break;
+               case FSCACHE_COOKIE_TYPE_DATAFILE:      type = 'E';     break;
+               default:                                type = 'T';     break;
+               }
+       }
+
+       key[mark] = type;
+       key[len] = 0;
+
+       _leave(" = %p %d:[%s]", key, len, key);
+       return key;
+}