diff options
Diffstat (limited to 'kernel/fs/dcookies.c')
-rw-r--r-- | kernel/fs/dcookies.c | 350 |
1 files changed, 350 insertions, 0 deletions
diff --git a/kernel/fs/dcookies.c b/kernel/fs/dcookies.c new file mode 100644 index 000000000..ac44a69fb --- /dev/null +++ b/kernel/fs/dcookies.c @@ -0,0 +1,350 @@ +/* + * dcookies.c + * + * Copyright 2002 John Levon <levon@movementarian.org> + * + * Persistent cookie-path mappings. These are used by + * profilers to convert a per-task EIP value into something + * non-transitory that can be processed at a later date. + * This is done by locking the dentry/vfsmnt pair in the + * kernel until released by the tasks needing the persistent + * objects. The tag is simply an unsigned long that refers + * to the pair and can be looked up from userspace. + */ + +#include <linux/syscalls.h> +#include <linux/export.h> +#include <linux/slab.h> +#include <linux/list.h> +#include <linux/mount.h> +#include <linux/capability.h> +#include <linux/dcache.h> +#include <linux/mm.h> +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/dcookies.h> +#include <linux/mutex.h> +#include <linux/path.h> +#include <linux/compat.h> +#include <asm/uaccess.h> + +/* The dcookies are allocated from a kmem_cache and + * hashed onto a small number of lists. None of the + * code here is particularly performance critical + */ +struct dcookie_struct { + struct path path; + struct list_head hash_list; +}; + +static LIST_HEAD(dcookie_users); +static DEFINE_MUTEX(dcookie_mutex); +static struct kmem_cache *dcookie_cache __read_mostly; +static struct list_head *dcookie_hashtable __read_mostly; +static size_t hash_size __read_mostly; + +static inline int is_live(void) +{ + return !(list_empty(&dcookie_users)); +} + + +/* The dentry is locked, its address will do for the cookie */ +static inline unsigned long dcookie_value(struct dcookie_struct * dcs) +{ + return (unsigned long)dcs->path.dentry; +} + + +static size_t dcookie_hash(unsigned long dcookie) +{ + return (dcookie >> L1_CACHE_SHIFT) & (hash_size - 1); +} + + +static struct dcookie_struct * find_dcookie(unsigned long dcookie) +{ + struct dcookie_struct *found = NULL; + struct dcookie_struct * dcs; + struct list_head * pos; + struct list_head * list; + + list = dcookie_hashtable + dcookie_hash(dcookie); + + list_for_each(pos, list) { + dcs = list_entry(pos, struct dcookie_struct, hash_list); + if (dcookie_value(dcs) == dcookie) { + found = dcs; + break; + } + } + + return found; +} + + +static void hash_dcookie(struct dcookie_struct * dcs) +{ + struct list_head * list = dcookie_hashtable + dcookie_hash(dcookie_value(dcs)); + list_add(&dcs->hash_list, list); +} + + +static struct dcookie_struct *alloc_dcookie(struct path *path) +{ + struct dcookie_struct *dcs = kmem_cache_alloc(dcookie_cache, + GFP_KERNEL); + struct dentry *d; + if (!dcs) + return NULL; + + d = path->dentry; + spin_lock(&d->d_lock); + d->d_flags |= DCACHE_COOKIE; + spin_unlock(&d->d_lock); + + dcs->path = *path; + path_get(path); + hash_dcookie(dcs); + return dcs; +} + + +/* This is the main kernel-side routine that retrieves the cookie + * value for a dentry/vfsmnt pair. + */ +int get_dcookie(struct path *path, unsigned long *cookie) +{ + int err = 0; + struct dcookie_struct * dcs; + + mutex_lock(&dcookie_mutex); + + if (!is_live()) { + err = -EINVAL; + goto out; + } + + if (path->dentry->d_flags & DCACHE_COOKIE) { + dcs = find_dcookie((unsigned long)path->dentry); + } else { + dcs = alloc_dcookie(path); + if (!dcs) { + err = -ENOMEM; + goto out; + } + } + + *cookie = dcookie_value(dcs); + +out: + mutex_unlock(&dcookie_mutex); + return err; +} + + +/* And here is where the userspace process can look up the cookie value + * to retrieve the path. + */ +SYSCALL_DEFINE3(lookup_dcookie, u64, cookie64, char __user *, buf, size_t, len) +{ + unsigned long cookie = (unsigned long)cookie64; + int err = -EINVAL; + char * kbuf; + char * path; + size_t pathlen; + struct dcookie_struct * dcs; + + /* we could leak path information to users + * without dir read permission without this + */ + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + mutex_lock(&dcookie_mutex); + + if (!is_live()) { + err = -EINVAL; + goto out; + } + + if (!(dcs = find_dcookie(cookie))) + goto out; + + err = -ENOMEM; + kbuf = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (!kbuf) + goto out; + + /* FIXME: (deleted) ? */ + path = d_path(&dcs->path, kbuf, PAGE_SIZE); + + mutex_unlock(&dcookie_mutex); + + if (IS_ERR(path)) { + err = PTR_ERR(path); + goto out_free; + } + + err = -ERANGE; + + pathlen = kbuf + PAGE_SIZE - path; + if (pathlen <= len) { + err = pathlen; + if (copy_to_user(buf, path, pathlen)) + err = -EFAULT; + } + +out_free: + kfree(kbuf); + return err; +out: + mutex_unlock(&dcookie_mutex); + return err; +} + +#ifdef CONFIG_COMPAT +COMPAT_SYSCALL_DEFINE4(lookup_dcookie, u32, w0, u32, w1, char __user *, buf, compat_size_t, len) +{ +#ifdef __BIG_ENDIAN + return sys_lookup_dcookie(((u64)w0 << 32) | w1, buf, len); +#else + return sys_lookup_dcookie(((u64)w1 << 32) | w0, buf, len); +#endif +} +#endif + +static int dcookie_init(void) +{ + struct list_head * d; + unsigned int i, hash_bits; + int err = -ENOMEM; + + dcookie_cache = kmem_cache_create("dcookie_cache", + sizeof(struct dcookie_struct), + 0, 0, NULL); + + if (!dcookie_cache) + goto out; + + dcookie_hashtable = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (!dcookie_hashtable) + goto out_kmem; + + err = 0; + + /* + * Find the power-of-two list-heads that can fit into the allocation.. + * We don't guarantee that "sizeof(struct list_head)" is necessarily + * a power-of-two. + */ + hash_size = PAGE_SIZE / sizeof(struct list_head); + hash_bits = 0; + do { + hash_bits++; + } while ((hash_size >> hash_bits) != 0); + hash_bits--; + + /* + * Re-calculate the actual number of entries and the mask + * from the number of bits we can fit. + */ + hash_size = 1UL << hash_bits; + + /* And initialize the newly allocated array */ + d = dcookie_hashtable; + i = hash_size; + do { + INIT_LIST_HEAD(d); + d++; + i--; + } while (i); + +out: + return err; +out_kmem: + kmem_cache_destroy(dcookie_cache); + goto out; +} + + +static void free_dcookie(struct dcookie_struct * dcs) +{ + struct dentry *d = dcs->path.dentry; + + spin_lock(&d->d_lock); + d->d_flags &= ~DCACHE_COOKIE; + spin_unlock(&d->d_lock); + + path_put(&dcs->path); + kmem_cache_free(dcookie_cache, dcs); +} + + +static void dcookie_exit(void) +{ + struct list_head * list; + struct list_head * pos; + struct list_head * pos2; + struct dcookie_struct * dcs; + size_t i; + + for (i = 0; i < hash_size; ++i) { + list = dcookie_hashtable + i; + list_for_each_safe(pos, pos2, list) { + dcs = list_entry(pos, struct dcookie_struct, hash_list); + list_del(&dcs->hash_list); + free_dcookie(dcs); + } + } + + kfree(dcookie_hashtable); + kmem_cache_destroy(dcookie_cache); +} + + +struct dcookie_user { + struct list_head next; +}; + +struct dcookie_user * dcookie_register(void) +{ + struct dcookie_user * user; + + mutex_lock(&dcookie_mutex); + + user = kmalloc(sizeof(struct dcookie_user), GFP_KERNEL); + if (!user) + goto out; + + if (!is_live() && dcookie_init()) + goto out_free; + + list_add(&user->next, &dcookie_users); + +out: + mutex_unlock(&dcookie_mutex); + return user; +out_free: + kfree(user); + user = NULL; + goto out; +} + + +void dcookie_unregister(struct dcookie_user * user) +{ + mutex_lock(&dcookie_mutex); + + list_del(&user->next); + kfree(user); + + if (!is_live()) + dcookie_exit(); + + mutex_unlock(&dcookie_mutex); +} + +EXPORT_SYMBOL_GPL(dcookie_register); +EXPORT_SYMBOL_GPL(dcookie_unregister); +EXPORT_SYMBOL_GPL(get_dcookie); |