linux/fs/dcookies.c
<<
>>
Prefs
   1/*
   2 * dcookies.c
   3 *
   4 * Copyright 2002 John Levon <levon@movementarian.org>
   5 *
   6 * Persistent cookie-path mappings. These are used by
   7 * profilers to convert a per-task EIP value into something
   8 * non-transitory that can be processed at a later date.
   9 * This is done by locking the dentry/vfsmnt pair in the
  10 * kernel until released by the tasks needing the persistent
  11 * objects. The tag is simply an unsigned long that refers
  12 * to the pair and can be looked up from userspace.
  13 */
  14
  15#include <linux/syscalls.h>
  16#include <linux/export.h>
  17#include <linux/slab.h>
  18#include <linux/list.h>
  19#include <linux/mount.h>
  20#include <linux/capability.h>
  21#include <linux/dcache.h>
  22#include <linux/mm.h>
  23#include <linux/err.h>
  24#include <linux/errno.h>
  25#include <linux/dcookies.h>
  26#include <linux/mutex.h>
  27#include <linux/path.h>
  28#include <linux/compat.h>
  29#include <asm/uaccess.h>
  30
  31/* The dcookies are allocated from a kmem_cache and
  32 * hashed onto a small number of lists. None of the
  33 * code here is particularly performance critical
  34 */
  35struct dcookie_struct {
  36        struct path path;
  37        struct list_head hash_list;
  38};
  39
  40static LIST_HEAD(dcookie_users);
  41static DEFINE_MUTEX(dcookie_mutex);
  42static struct kmem_cache *dcookie_cache __read_mostly;
  43static struct list_head *dcookie_hashtable __read_mostly;
  44static size_t hash_size __read_mostly;
  45
  46static inline int is_live(void)
  47{
  48        return !(list_empty(&dcookie_users));
  49}
  50
  51
  52/* The dentry is locked, its address will do for the cookie */
  53static inline unsigned long dcookie_value(struct dcookie_struct * dcs)
  54{
  55        return (unsigned long)dcs->path.dentry;
  56}
  57
  58
  59static size_t dcookie_hash(unsigned long dcookie)
  60{
  61        return (dcookie >> L1_CACHE_SHIFT) & (hash_size - 1);
  62}
  63
  64
  65static struct dcookie_struct * find_dcookie(unsigned long dcookie)
  66{
  67        struct dcookie_struct *found = NULL;
  68        struct dcookie_struct * dcs;
  69        struct list_head * pos;
  70        struct list_head * list;
  71
  72        list = dcookie_hashtable + dcookie_hash(dcookie);
  73
  74        list_for_each(pos, list) {
  75                dcs = list_entry(pos, struct dcookie_struct, hash_list);
  76                if (dcookie_value(dcs) == dcookie) {
  77                        found = dcs;
  78                        break;
  79                }
  80        }
  81
  82        return found;
  83}
  84
  85
  86static void hash_dcookie(struct dcookie_struct * dcs)
  87{
  88        struct list_head * list = dcookie_hashtable + dcookie_hash(dcookie_value(dcs));
  89        list_add(&dcs->hash_list, list);
  90}
  91
  92
  93static struct dcookie_struct *alloc_dcookie(struct path *path)
  94{
  95        struct dcookie_struct *dcs = kmem_cache_alloc(dcookie_cache,
  96                                                        GFP_KERNEL);
  97        struct dentry *d;
  98        if (!dcs)
  99                return NULL;
 100
 101        d = path->dentry;
 102        spin_lock(&d->d_lock);
 103        d->d_flags |= DCACHE_COOKIE;
 104        spin_unlock(&d->d_lock);
 105
 106        dcs->path = *path;
 107        path_get(path);
 108        hash_dcookie(dcs);
 109        return dcs;
 110}
 111
 112
 113/* This is the main kernel-side routine that retrieves the cookie
 114 * value for a dentry/vfsmnt pair.
 115 */
 116int get_dcookie(struct path *path, unsigned long *cookie)
 117{
 118        int err = 0;
 119        struct dcookie_struct * dcs;
 120
 121        mutex_lock(&dcookie_mutex);
 122
 123        if (!is_live()) {
 124                err = -EINVAL;
 125                goto out;
 126        }
 127
 128        if (path->dentry->d_flags & DCACHE_COOKIE) {
 129                dcs = find_dcookie((unsigned long)path->dentry);
 130        } else {
 131                dcs = alloc_dcookie(path);
 132                if (!dcs) {
 133                        err = -ENOMEM;
 134                        goto out;
 135                }
 136        }
 137
 138        *cookie = dcookie_value(dcs);
 139
 140out:
 141        mutex_unlock(&dcookie_mutex);
 142        return err;
 143}
 144
 145
 146/* And here is where the userspace process can look up the cookie value
 147 * to retrieve the path.
 148 */
 149SYSCALL_DEFINE3(lookup_dcookie, u64, cookie64, char __user *, buf, size_t, len)
 150{
 151        unsigned long cookie = (unsigned long)cookie64;
 152        int err = -EINVAL;
 153        char * kbuf;
 154        char * path;
 155        size_t pathlen;
 156        struct dcookie_struct * dcs;
 157
 158        /* we could leak path information to users
 159         * without dir read permission without this
 160         */
 161        if (!capable(CAP_SYS_ADMIN))
 162                return -EPERM;
 163
 164        mutex_lock(&dcookie_mutex);
 165
 166        if (!is_live()) {
 167                err = -EINVAL;
 168                goto out;
 169        }
 170
 171        if (!(dcs = find_dcookie(cookie)))
 172                goto out;
 173
 174        err = -ENOMEM;
 175        kbuf = kmalloc(PAGE_SIZE, GFP_KERNEL);
 176        if (!kbuf)
 177                goto out;
 178
 179        /* FIXME: (deleted) ? */
 180        path = d_path(&dcs->path, kbuf, PAGE_SIZE);
 181
 182        mutex_unlock(&dcookie_mutex);
 183
 184        if (IS_ERR(path)) {
 185                err = PTR_ERR(path);
 186                goto out_free;
 187        }
 188
 189        err = -ERANGE;
 190 
 191        pathlen = kbuf + PAGE_SIZE - path;
 192        if (pathlen <= len) {
 193                err = pathlen;
 194                if (copy_to_user(buf, path, pathlen))
 195                        err = -EFAULT;
 196        }
 197
 198out_free:
 199        kfree(kbuf);
 200        return err;
 201out:
 202        mutex_unlock(&dcookie_mutex);
 203        return err;
 204}
 205
 206#ifdef CONFIG_COMPAT
 207COMPAT_SYSCALL_DEFINE4(lookup_dcookie, u32, w0, u32, w1, char __user *, buf, compat_size_t, len)
 208{
 209#ifdef __BIG_ENDIAN
 210        return sys_lookup_dcookie(((u64)w0 << 32) | w1, buf, len);
 211#else
 212        return sys_lookup_dcookie(((u64)w1 << 32) | w0, buf, len);
 213#endif
 214}
 215#endif
 216
 217static int dcookie_init(void)
 218{
 219        struct list_head * d;
 220        unsigned int i, hash_bits;
 221        int err = -ENOMEM;
 222
 223        dcookie_cache = kmem_cache_create("dcookie_cache",
 224                sizeof(struct dcookie_struct),
 225                0, 0, NULL);
 226
 227        if (!dcookie_cache)
 228                goto out;
 229
 230        dcookie_hashtable = kmalloc(PAGE_SIZE, GFP_KERNEL);
 231        if (!dcookie_hashtable)
 232                goto out_kmem;
 233
 234        err = 0;
 235
 236        /*
 237         * Find the power-of-two list-heads that can fit into the allocation..
 238         * We don't guarantee that "sizeof(struct list_head)" is necessarily
 239         * a power-of-two.
 240         */
 241        hash_size = PAGE_SIZE / sizeof(struct list_head);
 242        hash_bits = 0;
 243        do {
 244                hash_bits++;
 245        } while ((hash_size >> hash_bits) != 0);
 246        hash_bits--;
 247
 248        /*
 249         * Re-calculate the actual number of entries and the mask
 250         * from the number of bits we can fit.
 251         */
 252        hash_size = 1UL << hash_bits;
 253
 254        /* And initialize the newly allocated array */
 255        d = dcookie_hashtable;
 256        i = hash_size;
 257        do {
 258                INIT_LIST_HEAD(d);
 259                d++;
 260                i--;
 261        } while (i);
 262
 263out:
 264        return err;
 265out_kmem:
 266        kmem_cache_destroy(dcookie_cache);
 267        goto out;
 268}
 269
 270
 271static void free_dcookie(struct dcookie_struct * dcs)
 272{
 273        struct dentry *d = dcs->path.dentry;
 274
 275        spin_lock(&d->d_lock);
 276        d->d_flags &= ~DCACHE_COOKIE;
 277        spin_unlock(&d->d_lock);
 278
 279        path_put(&dcs->path);
 280        kmem_cache_free(dcookie_cache, dcs);
 281}
 282
 283
 284static void dcookie_exit(void)
 285{
 286        struct list_head * list;
 287        struct list_head * pos;
 288        struct list_head * pos2;
 289        struct dcookie_struct * dcs;
 290        size_t i;
 291
 292        for (i = 0; i < hash_size; ++i) {
 293                list = dcookie_hashtable + i;
 294                list_for_each_safe(pos, pos2, list) {
 295                        dcs = list_entry(pos, struct dcookie_struct, hash_list);
 296                        list_del(&dcs->hash_list);
 297                        free_dcookie(dcs);
 298                }
 299        }
 300
 301        kfree(dcookie_hashtable);
 302        kmem_cache_destroy(dcookie_cache);
 303}
 304
 305
 306struct dcookie_user {
 307        struct list_head next;
 308};
 309 
 310struct dcookie_user * dcookie_register(void)
 311{
 312        struct dcookie_user * user;
 313
 314        mutex_lock(&dcookie_mutex);
 315
 316        user = kmalloc(sizeof(struct dcookie_user), GFP_KERNEL);
 317        if (!user)
 318                goto out;
 319
 320        if (!is_live() && dcookie_init())
 321                goto out_free;
 322
 323        list_add(&user->next, &dcookie_users);
 324
 325out:
 326        mutex_unlock(&dcookie_mutex);
 327        return user;
 328out_free:
 329        kfree(user);
 330        user = NULL;
 331        goto out;
 332}
 333
 334
 335void dcookie_unregister(struct dcookie_user * user)
 336{
 337        mutex_lock(&dcookie_mutex);
 338
 339        list_del(&user->next);
 340        kfree(user);
 341
 342        if (!is_live())
 343                dcookie_exit();
 344
 345        mutex_unlock(&dcookie_mutex);
 346}
 347
 348EXPORT_SYMBOL_GPL(dcookie_register);
 349EXPORT_SYMBOL_GPL(dcookie_unregister);
 350EXPORT_SYMBOL_GPL(get_dcookie);
 351