📄 dm-ioctl.c
字号:
/* * Copyright (C) 2001, 2002 Sistina Software (UK) Limited. * * This file is released under the GPL. */#include "dm.h"#include <linux/module.h>#include <linux/vmalloc.h>#include <linux/miscdevice.h>#include <linux/init.h>#include <linux/wait.h>#include <linux/slab.h>#include <linux/devfs_fs_kernel.h>#include <linux/dm-ioctl.h>#include <asm/uaccess.h>#define DM_DRIVER_EMAIL "dm@uk.sistina.com"/*----------------------------------------------------------------- * The ioctl interface needs to be able to look up devices by * name or uuid. *---------------------------------------------------------------*/struct hash_cell { struct list_head name_list; struct list_head uuid_list; char *name; char *uuid; struct mapped_device *md; struct dm_table *new_map;};struct vers_iter { size_t param_size; struct dm_target_versions *vers, *old_vers; char *end; uint32_t flags;};#define NUM_BUCKETS 64#define MASK_BUCKETS (NUM_BUCKETS - 1)static struct list_head _name_buckets[NUM_BUCKETS];static struct list_head _uuid_buckets[NUM_BUCKETS];static void dm_hash_remove_all(void);/* * Guards access to both hash tables. */static DECLARE_RWSEM(_hash_lock);static void init_buckets(struct list_head *buckets){ unsigned int i; for (i = 0; i < NUM_BUCKETS; i++) INIT_LIST_HEAD(buckets + i);}static int dm_hash_init(void){ init_buckets(_name_buckets); init_buckets(_uuid_buckets); devfs_mk_dir(DM_DIR); return 0;}static void dm_hash_exit(void){ dm_hash_remove_all(); devfs_remove(DM_DIR);}/*----------------------------------------------------------------- * Hash function: * We're not really concerned with the str hash function being * fast since it's only used by the ioctl interface. *---------------------------------------------------------------*/static unsigned int hash_str(const char *str){ const unsigned int hash_mult = 2654435387U; unsigned int h = 0; while (*str) h = (h + (unsigned int) *str++) * hash_mult; return h & MASK_BUCKETS;}/*----------------------------------------------------------------- * Code for looking up a device by name *---------------------------------------------------------------*/static struct hash_cell *__get_name_cell(const char *str){ struct hash_cell *hc; unsigned int h = hash_str(str); list_for_each_entry (hc, _name_buckets + h, name_list) if (!strcmp(hc->name, str)) return hc; return NULL;}static struct hash_cell *__get_uuid_cell(const char *str){ struct hash_cell *hc; unsigned int h = hash_str(str); list_for_each_entry (hc, _uuid_buckets + h, uuid_list) if (!strcmp(hc->uuid, str)) return hc; return NULL;}/*----------------------------------------------------------------- * Inserting, removing and renaming a device. *---------------------------------------------------------------*/static inline char *kstrdup(const char *str){ char *r = kmalloc(strlen(str) + 1, GFP_KERNEL); if (r) strcpy(r, str); return r;}static struct hash_cell *alloc_cell(const char *name, const char *uuid, struct mapped_device *md){ struct hash_cell *hc; hc = kmalloc(sizeof(*hc), GFP_KERNEL); if (!hc) return NULL; hc->name = kstrdup(name); if (!hc->name) { kfree(hc); return NULL; } if (!uuid) hc->uuid = NULL; else { hc->uuid = kstrdup(uuid); if (!hc->uuid) { kfree(hc->name); kfree(hc); return NULL; } } INIT_LIST_HEAD(&hc->name_list); INIT_LIST_HEAD(&hc->uuid_list); hc->md = md; hc->new_map = NULL; return hc;}static void free_cell(struct hash_cell *hc){ if (hc) { kfree(hc->name); kfree(hc->uuid); kfree(hc); }}/* * devfs stuff. */static int register_with_devfs(struct hash_cell *hc){ struct gendisk *disk = dm_disk(hc->md); devfs_mk_bdev(MKDEV(disk->major, disk->first_minor), S_IFBLK | S_IRUSR | S_IWUSR | S_IRGRP, DM_DIR "/%s", hc->name); return 0;}static int unregister_with_devfs(struct hash_cell *hc){ devfs_remove(DM_DIR"/%s", hc->name); return 0;}/* * The kdev_t and uuid of a device can never change once it is * initially inserted. */static int dm_hash_insert(const char *name, const char *uuid, struct mapped_device *md){ struct hash_cell *cell; /* * Allocate the new cells. */ cell = alloc_cell(name, uuid, md); if (!cell) return -ENOMEM; /* * Insert the cell into both hash tables. */ down_write(&_hash_lock); if (__get_name_cell(name)) goto bad; list_add(&cell->name_list, _name_buckets + hash_str(name)); if (uuid) { if (__get_uuid_cell(uuid)) { list_del(&cell->name_list); goto bad; } list_add(&cell->uuid_list, _uuid_buckets + hash_str(uuid)); } register_with_devfs(cell); dm_get(md); up_write(&_hash_lock); return 0; bad: up_write(&_hash_lock); free_cell(cell); return -EBUSY;}static void __hash_remove(struct hash_cell *hc){ /* remove from the dev hash */ list_del(&hc->uuid_list); list_del(&hc->name_list); unregister_with_devfs(hc); dm_put(hc->md); if (hc->new_map) dm_table_put(hc->new_map); free_cell(hc);}static void dm_hash_remove_all(void){ int i; struct hash_cell *hc; struct list_head *tmp, *n; down_write(&_hash_lock); for (i = 0; i < NUM_BUCKETS; i++) { list_for_each_safe (tmp, n, _name_buckets + i) { hc = list_entry(tmp, struct hash_cell, name_list); __hash_remove(hc); } } up_write(&_hash_lock);}static int dm_hash_rename(const char *old, const char *new){ char *new_name, *old_name; struct hash_cell *hc; /* * duplicate new. */ new_name = kstrdup(new); if (!new_name) return -ENOMEM; down_write(&_hash_lock); /* * Is new free ? */ hc = __get_name_cell(new); if (hc) { DMWARN("asked to rename to an already existing name %s -> %s", old, new); up_write(&_hash_lock); kfree(new_name); return -EBUSY; } /* * Is there such a device as 'old' ? */ hc = __get_name_cell(old); if (!hc) { DMWARN("asked to rename a non existent device %s -> %s", old, new); up_write(&_hash_lock); kfree(new_name); return -ENXIO; } /* * rename and move the name cell. */ unregister_with_devfs(hc); list_del(&hc->name_list); old_name = hc->name; hc->name = new_name; list_add(&hc->name_list, _name_buckets + hash_str(new_name)); /* rename the device node in devfs */ register_with_devfs(hc); up_write(&_hash_lock); kfree(old_name); return 0;}/*----------------------------------------------------------------- * Implementation of the ioctl commands *---------------------------------------------------------------*//* * All the ioctl commands get dispatched to functions with this * prototype. */typedef int (*ioctl_fn)(struct dm_ioctl *param, size_t param_size);static int remove_all(struct dm_ioctl *param, size_t param_size){ dm_hash_remove_all(); param->data_size = 0; return 0;}/* * Round up the ptr to an 8-byte boundary. */#define ALIGN_MASK 7static inline void *align_ptr(void *ptr){ return (void *) (((size_t) (ptr + ALIGN_MASK)) & ~ALIGN_MASK);}/* * Retrieves the data payload buffer from an already allocated * struct dm_ioctl. */static void *get_result_buffer(struct dm_ioctl *param, size_t param_size, size_t *len){ param->data_start = align_ptr(param + 1) - (void *) param; if (param->data_start < param_size) *len = param_size - param->data_start; else *len = 0; return ((void *) param) + param->data_start;}static int list_devices(struct dm_ioctl *param, size_t param_size){ unsigned int i; struct hash_cell *hc; size_t len, needed = 0; struct gendisk *disk; struct dm_name_list *nl, *old_nl = NULL; down_write(&_hash_lock); /* * Loop through all the devices working out how much * space we need. */ for (i = 0; i < NUM_BUCKETS; i++) { list_for_each_entry (hc, _name_buckets + i, name_list) { needed += sizeof(struct dm_name_list); needed += strlen(hc->name) + 1; needed += ALIGN_MASK; } } /* * Grab our output buffer. */ nl = get_result_buffer(param, param_size, &len); if (len < needed) { param->flags |= DM_BUFFER_FULL_FLAG; goto out; } param->data_size = param->data_start + needed; nl->dev = 0; /* Flags no data */ /* * Now loop through filling out the names. */ for (i = 0; i < NUM_BUCKETS; i++) { list_for_each_entry (hc, _name_buckets + i, name_list) { if (old_nl) old_nl->next = (uint32_t) ((void *) nl - (void *) old_nl); disk = dm_disk(hc->md); nl->dev = huge_encode_dev(MKDEV(disk->major, disk->first_minor)); nl->next = 0; strcpy(nl->name, hc->name); old_nl = nl; nl = align_ptr(((void *) ++nl) + strlen(hc->name) + 1); } } out: up_write(&_hash_lock); return 0;}static void list_version_get_needed(struct target_type *tt, void *needed_param){ size_t *needed = needed_param; *needed += strlen(tt->name); *needed += sizeof(tt->version); *needed += ALIGN_MASK;}static void list_version_get_info(struct target_type *tt, void *param){ struct vers_iter *info = param; /* Check space - it might have changed since the first iteration */ if ((char *)info->vers + sizeof(tt->version) + strlen(tt->name) + 1 > info->end) { info->flags = DM_BUFFER_FULL_FLAG; return; } if (info->old_vers) info->old_vers->next = (uint32_t) ((void *)info->vers - (void *)info->old_vers); info->vers->version[0] = tt->version[0]; info->vers->version[1] = tt->version[1]; info->vers->version[2] = tt->version[2]; info->vers->next = 0; strcpy(info->vers->name, tt->name); info->old_vers = info->vers; info->vers = align_ptr(((void *) ++info->vers) + strlen(tt->name) + 1);}static int list_versions(struct dm_ioctl *param, size_t param_size){ size_t len, needed = 0; struct dm_target_versions *vers; struct vers_iter iter_info; /* * Loop through all the devices working out how much * space we need. */ dm_target_iterate(list_version_get_needed, &needed); /* * Grab our output buffer. */ vers = get_result_buffer(param, param_size, &len); if (len < needed) { param->flags |= DM_BUFFER_FULL_FLAG; goto out; } param->data_size = param->data_start + needed; iter_info.param_size = param_size; iter_info.old_vers = NULL; iter_info.vers = vers; iter_info.flags = 0; iter_info.end = (char *)vers+len; /* * Now loop through filling out the names & versions. */ dm_target_iterate(list_version_get_info, &iter_info); param->flags |= iter_info.flags; out: return 0;}static int check_name(const char *name){ if (strchr(name, '/')) { DMWARN("invalid device name"); return -EINVAL; } return 0;}/* * Fills in a dm_ioctl structure, ready for sending back to * userland. */static int __dev_status(struct mapped_device *md, struct dm_ioctl *param){ struct gendisk *disk = dm_disk(md); struct dm_table *table; struct block_device *bdev; param->flags &= ~(DM_SUSPEND_FLAG | DM_READONLY_FLAG | DM_ACTIVE_PRESENT_FLAG); if (dm_suspended(md)) param->flags |= DM_SUSPEND_FLAG; bdev = bdget_disk(disk, 0); if (!bdev) return -ENXIO; param->dev = huge_encode_dev(MKDEV(disk->major, disk->first_minor)); /* * Yes, this will be out of date by the time it gets back * to userland, but it is still very useful ofr * debugging. */ param->open_count = bdev->bd_openers; bdput(bdev); if (disk->policy) param->flags |= DM_READONLY_FLAG; param->event_nr = dm_get_event_nr(md); table = dm_get_table(md); if (table) { param->flags |= DM_ACTIVE_PRESENT_FLAG; param->target_count = dm_table_get_num_targets(table); dm_table_put(table); } else param->target_count = 0; return 0;}static int dev_create(struct dm_ioctl *param, size_t param_size){ int r; struct mapped_device *md; r = check_name(param->name); if (r) return r; if (param->flags & DM_PERSISTENT_DEV_FLAG) r = dm_create_with_minor(MINOR(huge_decode_dev(param->dev)), &md); else r = dm_create(&md); if (r) return r; r = dm_hash_insert(param->name, *param->uuid ? param->uuid : NULL, md); if (r) { dm_put(md); return r; } param->flags &= ~DM_INACTIVE_PRESENT_FLAG; r = __dev_status(md, param); dm_put(md); return r;}/* * Always use UUID for lookups if it's present, otherwise use name. */static inline struct hash_cell *__find_device_hash_cell(struct dm_ioctl *param){ return *param->uuid ? __get_uuid_cell(param->uuid) : __get_name_cell(param->name);}static inline struct mapped_device *find_device(struct dm_ioctl *param){ struct hash_cell *hc; struct mapped_device *md = NULL; down_read(&_hash_lock); hc = __find_device_hash_cell(param); if (hc) { md = hc->md; /* * Sneakily write in both the name and the uuid * while we have the cell. */ strncpy(param->name, hc->name, sizeof(param->name)); if (hc->uuid) strncpy(param->uuid, hc->uuid, sizeof(param->uuid)-1); else param->uuid[0] = '\0'; if (hc->new_map) param->flags |= DM_INACTIVE_PRESENT_FLAG; else param->flags &= ~DM_INACTIVE_PRESENT_FLAG; dm_get(md); } up_read(&_hash_lock); return md;}static int dev_remove(struct dm_ioctl *param, size_t param_size){ struct hash_cell *hc; down_write(&_hash_lock); hc = __find_device_hash_cell(param); if (!hc) { DMWARN("device doesn't appear to be in the dev hash table."); up_write(&_hash_lock); return -ENXIO; } __hash_remove(hc); up_write(&_hash_lock); param->data_size = 0; return 0;}/* * Check a string doesn't overrun the chunk of * memory we copied from userland. */static int invalid_str(char *str, void *end){ while ((void *) str < end) if (!*str++) return 0; return -EINVAL;}static int dev_rename(struct dm_ioctl *param, size_t param_size){ int r; char *new_name = (char *) param + param->data_start; if (new_name < (char *) (param + 1) || invalid_str(new_name, (void *) param + param_size)) { DMWARN("Invalid new logical volume name supplied."); return -EINVAL; } r = check_name(new_name); if (r) return r; param->data_size = 0; return dm_hash_rename(param->name, new_name);}static int do_suspend(struct dm_ioctl *param)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -