| /****************************************************************************** |
| ******************************************************************************* |
| ** |
| ** Copyright (C) Sistina Software, Inc. 1997-2003 All rights reserved. |
| ** Copyright (C) 2004-2006 Red Hat, Inc. All rights reserved. |
| ** |
| ** This copyrighted material is made available to anyone wishing to use, |
| ** modify, copy, or redistribute it subject to the terms and conditions |
| ** of the GNU General Public License v.2. |
| ** |
| ******************************************************************************* |
| ******************************************************************************/ |
| |
| /* |
| * device.c |
| * |
| * This is the userland interface to the DLM. |
| * |
| * The locking is done via a misc char device (find the |
| * registered minor number in /proc/misc). |
| * |
| * User code should not use this interface directly but |
| * call the library routines in libdlm.a instead. |
| * |
| */ |
| |
| #include <linux/miscdevice.h> |
| #include <linux/init.h> |
| #include <linux/wait.h> |
| #include <linux/module.h> |
| #include <linux/file.h> |
| #include <linux/fs.h> |
| #include <linux/poll.h> |
| #include <linux/signal.h> |
| #include <linux/spinlock.h> |
| #include <linux/idr.h> |
| |
| #include <linux/dlm.h> |
| #include <linux/dlm_device.h> |
| |
| #include "lvb_table.h" |
| |
| static struct file_operations _dlm_fops; |
| static const char *name_prefix="dlm"; |
| static struct list_head user_ls_list; |
| static struct mutex user_ls_lock; |
| |
| /* Flags in li_flags */ |
| #define LI_FLAG_COMPLETE 1 |
| #define LI_FLAG_FIRSTLOCK 2 |
| #define LI_FLAG_PERSISTENT 3 |
| #define LI_FLAG_ONLIST 4 |
| |
| /* flags in ls_flags*/ |
| #define LS_FLAG_DELETED 1 |
| #define LS_FLAG_AUTOFREE 2 |
| |
| /* flags in ls_flags*/ |
| #define FI_FLAG_OPEN 1 |
| #define FI_FLAG_COMPAT 2 |
| |
| #define LOCKINFO_MAGIC 0x53595324 |
| |
| struct lock_info { |
| uint32_t li_magic; |
| uint8_t li_cmd; |
| int8_t li_grmode; |
| int8_t li_rqmode; |
| struct dlm_lksb li_lksb; |
| wait_queue_head_t li_waitq; |
| unsigned long li_flags; |
| void __user *li_castparam; |
| void __user *li_castaddr; |
| void __user *li_bastparam; |
| void __user *li_bastaddr; |
| void __user *li_pend_bastparam; |
| void __user *li_pend_bastaddr; |
| struct list_head li_ownerqueue; |
| struct file_info *li_file; |
| struct dlm_lksb __user *li_user_lksb; |
| struct completion li_firstcomp; |
| }; |
| |
| /* A queued AST no less */ |
| struct ast_info { |
| struct dlm_lock_result result; |
| struct list_head list; |
| uint32_t lvb_updated; |
| uint32_t progress; /* How much has been read */ |
| }; |
| |
| /* One of these per userland lockspace */ |
| struct user_ls { |
| void *ls_lockspace; |
| atomic_t ls_refcnt; |
| long ls_flags; |
| |
| /* Lock infos are stored in here indexed by lock ID */ |
| struct idr lockinfo_idr; |
| rwlock_t lockinfo_lock; |
| |
| /* Passed into misc_register() */ |
| struct miscdevice ls_miscinfo; |
| struct list_head ls_list; |
| }; |
| |
| /* misc_device info for the control device */ |
| static struct miscdevice ctl_device; |
| |
| /* |
| * Stuff we hang off the file struct. |
| * The first two are to cope with unlocking all the |
| * locks help by a process when it dies. |
| */ |
| struct file_info { |
| struct list_head fi_li_list; /* List of active lock_infos */ |
| spinlock_t fi_li_lock; |
| struct list_head fi_ast_list; /* Queue of ASTs to be delivered */ |
| spinlock_t fi_ast_lock; |
| wait_queue_head_t fi_wait; |
| struct user_ls *fi_ls; |
| atomic_t fi_refcnt; /* Number of users */ |
| unsigned long fi_flags; |
| }; |
| |
| #ifdef CONFIG_COMPAT |
| |
| struct dlm_lock_params32 { |
| __u8 mode; |
| __u8 namelen; |
| __u16 flags; |
| __u32 lkid; |
| __u32 parent; |
| |
| __u32 castparam; |
| __u32 castaddr; |
| __u32 bastparam; |
| __u32 bastaddr; |
| __u32 lksb; |
| |
| char lvb[DLM_USER_LVB_LEN]; |
| char name[0]; |
| }; |
| |
| struct dlm_write_request32 { |
| __u32 version[3]; |
| __u8 cmd; |
| __u8 is64bit; |
| __u8 unused[2]; |
| |
| union { |
| struct dlm_lock_params32 lock; |
| struct dlm_lspace_params lspace; |
| } i; |
| }; |
| |
| struct dlm_lksb32 { |
| __u32 sb_status; |
| __u32 sb_lkid; |
| __u8 sb_flags; |
| __u32 sb_lvbptr; |
| }; |
| |
| struct dlm_lock_result32 { |
| __u32 length; |
| __u32 user_astaddr; |
| __u32 user_astparam; |
| __u32 user_lksb; |
| struct dlm_lksb32 lksb; |
| __u8 bast_mode; |
| __u8 unused[3]; |
| /* Offsets may be zero if no data is present */ |
| __u32 lvb_offset; |
| }; |
| |
| |
| static void compat_input(struct dlm_write_request *kparams, struct dlm_write_request32 *k32params) |
| { |
| |
| kparams->version[0] = k32params->version[0]; |
| kparams->version[1] = k32params->version[1]; |
| kparams->version[2] = k32params->version[2]; |
| |
| kparams->cmd = k32params->cmd; |
| kparams->is64bit = k32params->is64bit; |
| if (kparams->cmd == DLM_USER_CREATE_LOCKSPACE || |
| kparams->cmd == DLM_USER_REMOVE_LOCKSPACE) { |
| |
| kparams->i.lspace.flags = k32params->i.lspace.flags; |
| kparams->i.lspace.minor = k32params->i.lspace.minor; |
| strcpy(kparams->i.lspace.name, k32params->i.lspace.name); |
| } |
| else { |
| kparams->i.lock.mode = k32params->i.lock.mode; |
| kparams->i.lock.namelen = k32params->i.lock.namelen; |
| kparams->i.lock.flags = k32params->i.lock.flags; |
| kparams->i.lock.lkid = k32params->i.lock.lkid; |
| kparams->i.lock.parent = k32params->i.lock.parent; |
| kparams->i.lock.castparam = (void *)(long)k32params->i.lock.castparam; |
| kparams->i.lock.castaddr = (void *)(long)k32params->i.lock.castaddr; |
| kparams->i.lock.bastparam = (void *)(long)k32params->i.lock.bastparam; |
| kparams->i.lock.bastaddr = (void *)(long)k32params->i.lock.bastaddr; |
| kparams->i.lock.lksb = (void *)(long)k32params->i.lock.lksb; |
| memcpy(kparams->i.lock.lvb, k32params->i.lock.lvb, DLM_USER_LVB_LEN); |
| memcpy(kparams->i.lock.name, k32params->i.lock.name, kparams->i.lock.namelen); |
| } |
| } |
| |
| void compat_output(struct dlm_lock_result *res, struct dlm_lock_result32 *res32) |
| { |
| res32->length = res->length - (sizeof(struct dlm_lock_result) - sizeof(struct dlm_lock_result32)); |
| res32->user_astaddr = (__u32)(long)res->user_astaddr; |
| res32->user_astparam = (__u32)(long)res->user_astparam; |
| res32->user_lksb = (__u32)(long)res->user_lksb; |
| res32->bast_mode = res->bast_mode; |
| |
| res32->lvb_offset = res->lvb_offset; |
| res32->length = res->length; |
| |
| res32->lksb.sb_status = res->lksb.sb_status; |
| res32->lksb.sb_flags = res->lksb.sb_flags; |
| res32->lksb.sb_lkid = res->lksb.sb_lkid; |
| res32->lksb.sb_lvbptr = (__u32)(long)res->lksb.sb_lvbptr; |
| } |
| #endif |
| |
| |
| /* get and put ops for file_info. |
| Actually I don't really like "get" and "put", but everyone |
| else seems to use them and I can't think of anything |
| nicer at the moment */ |
| static void get_file_info(struct file_info *f) |
| { |
| atomic_inc(&f->fi_refcnt); |
| } |
| |
| static void put_file_info(struct file_info *f) |
| { |
| if (atomic_dec_and_test(&f->fi_refcnt)) |
| kfree(f); |
| } |
| |
| static void release_lockinfo(struct user_ls *ls, struct lock_info *li) |
| { |
| put_file_info(li->li_file); |
| |
| write_lock(&ls->lockinfo_lock); |
| idr_remove(&ls->lockinfo_idr, li->li_lksb.sb_lkid); |
| write_unlock(&ls->lockinfo_lock); |
| |
| if (li->li_lksb.sb_lvbptr) |
| kfree(li->li_lksb.sb_lvbptr); |
| kfree(li); |
| |
| module_put(THIS_MODULE); |
| } |
| |
| static struct lock_info *get_lockinfo(struct user_ls *ls, uint32_t lockid) |
| { |
| struct lock_info *li; |
| |
| read_lock(&ls->lockinfo_lock); |
| li = idr_find(&ls->lockinfo_idr, lockid); |
| read_unlock(&ls->lockinfo_lock); |
| |
| return li; |
| } |
| |
| static int add_lockinfo(struct user_ls *ls, struct lock_info *li) |
| { |
| int n; |
| int r; |
| int ret = -EINVAL; |
| |
| write_lock(&ls->lockinfo_lock); |
| |
| if (idr_find(&ls->lockinfo_idr, li->li_lksb.sb_lkid)) |
| goto out_up; |
| |
| ret = -ENOMEM; |
| r = idr_pre_get(&ls->lockinfo_idr, GFP_KERNEL); |
| if (!r) |
| goto out_up; |
| |
| r = idr_get_new_above(&ls->lockinfo_idr, li, li->li_lksb.sb_lkid, &n); |
| if (r) |
| goto out_up; |
| |
| if (n != li->li_lksb.sb_lkid) { |
| idr_remove(&ls->lockinfo_idr, n); |
| goto out_up; |
| } |
| |
| ret = 0; |
| |
| out_up: |
| write_unlock(&ls->lockinfo_lock); |
| |
| return ret; |
| } |
| |
| |
| static struct user_ls *__find_lockspace(int minor) |
| { |
| struct user_ls *lsinfo; |
| |
| list_for_each_entry(lsinfo, &user_ls_list, ls_list) { |
| if (lsinfo->ls_miscinfo.minor == minor) |
| return lsinfo; |
| } |
| return NULL; |
| } |
| |
| /* Find a lockspace struct given the device minor number */ |
| static struct user_ls *find_lockspace(int minor) |
| { |
| struct user_ls *lsinfo; |
| |
| mutex_lock(&user_ls_lock); |
| lsinfo = __find_lockspace(minor); |
| mutex_unlock(&user_ls_lock); |
| |
| return lsinfo; |
| } |
| |
| static void add_lockspace_to_list(struct user_ls *lsinfo) |
| { |
| mutex_lock(&user_ls_lock); |
| list_add(&lsinfo->ls_list, &user_ls_list); |
| mutex_unlock(&user_ls_lock); |
| } |
| |
| /* Register a lockspace with the DLM and create a misc |
| device for userland to access it */ |
| static int register_lockspace(char *name, struct user_ls **ls, int flags) |
| { |
| struct user_ls *newls; |
| int status; |
| int namelen; |
| |
| namelen = strlen(name)+strlen(name_prefix)+2; |
| |
| newls = kzalloc(sizeof(struct user_ls), GFP_KERNEL); |
| if (!newls) |
| return -ENOMEM; |
| |
| newls->ls_miscinfo.name = kzalloc(namelen, GFP_KERNEL); |
| if (!newls->ls_miscinfo.name) { |
| kfree(newls); |
| return -ENOMEM; |
| } |
| |
| status = dlm_new_lockspace(name, strlen(name), &newls->ls_lockspace, 0, |
| DLM_USER_LVB_LEN); |
| if (status != 0) { |
| kfree(newls->ls_miscinfo.name); |
| kfree(newls); |
| return status; |
| } |
| |
| idr_init(&newls->lockinfo_idr); |
| rwlock_init(&newls->lockinfo_lock); |
| |
| snprintf((char*)newls->ls_miscinfo.name, namelen, "%s_%s", |
| name_prefix, name); |
| |
| newls->ls_miscinfo.fops = &_dlm_fops; |
| newls->ls_miscinfo.minor = MISC_DYNAMIC_MINOR; |
| |
| status = misc_register(&newls->ls_miscinfo); |
| if (status) { |
| printk(KERN_ERR "dlm: misc register failed for %s\n", name); |
| dlm_release_lockspace(newls->ls_lockspace, 0); |
| kfree(newls->ls_miscinfo.name); |
| kfree(newls); |
| return status; |
| } |
| |
| if (flags & DLM_USER_LSFLG_AUTOFREE) |
| set_bit(LS_FLAG_AUTOFREE, &newls->ls_flags); |
| |
| add_lockspace_to_list(newls); |
| *ls = newls; |
| return 0; |
| } |
| |
| /* Called with the user_ls_lock mutex held */ |
| static int unregister_lockspace(struct user_ls *lsinfo, int force) |
| { |
| int status; |
| |
| status = dlm_release_lockspace(lsinfo->ls_lockspace, force); |
| if (status) |
| return status; |
| |
| status = misc_deregister(&lsinfo->ls_miscinfo); |
| if (status) |
| return status; |
| |
| list_del(&lsinfo->ls_list); |
| set_bit(LS_FLAG_DELETED, &lsinfo->ls_flags); |
| lsinfo->ls_lockspace = NULL; |
| if (atomic_read(&lsinfo->ls_refcnt) == 0) { |
| kfree(lsinfo->ls_miscinfo.name); |
| kfree(lsinfo); |
| } |
| |
| return 0; |
| } |
| |
| /* Add it to userland's AST queue */ |
| static void add_to_astqueue(struct lock_info *li, void *astaddr, void *astparam, |
| int lvb_updated) |
| { |
| struct ast_info *ast = kzalloc(sizeof(struct ast_info), GFP_KERNEL); |
| if (!ast) |
| return; |
| |
| ast->result.user_astparam = astparam; |
| ast->result.user_astaddr = astaddr; |
| ast->result.user_lksb = li->li_user_lksb; |
| memcpy(&ast->result.lksb, &li->li_lksb, sizeof(struct dlm_lksb)); |
| ast->lvb_updated = lvb_updated; |
| |
| spin_lock(&li->li_file->fi_ast_lock); |
| list_add_tail(&ast->list, &li->li_file->fi_ast_list); |
| spin_unlock(&li->li_file->fi_ast_lock); |
| wake_up_interruptible(&li->li_file->fi_wait); |
| } |
| |
| static void bast_routine(void *param, int mode) |
| { |
| struct lock_info *li = param; |
| |
| if (li && li->li_bastaddr) |
| add_to_astqueue(li, li->li_bastaddr, li->li_bastparam, 0); |
| } |
| |
| /* |
| * This is the kernel's AST routine. |
| * All lock, unlock & query operations complete here. |
| * The only syncronous ops are those done during device close. |
| */ |
| static void ast_routine(void *param) |
| { |
| struct lock_info *li = param; |
| |
| /* Param may be NULL if a persistent lock is unlocked by someone else */ |
| if (!li) |
| return; |
| |
| /* If this is a succesful conversion then activate the blocking ast |
| * args from the conversion request */ |
| if (!test_bit(LI_FLAG_FIRSTLOCK, &li->li_flags) && |
| li->li_lksb.sb_status == 0) { |
| |
| li->li_bastparam = li->li_pend_bastparam; |
| li->li_bastaddr = li->li_pend_bastaddr; |
| li->li_pend_bastaddr = NULL; |
| } |
| |
| /* If it's an async request then post data to the user's AST queue. */ |
| if (li->li_castaddr) { |
| int lvb_updated = 0; |
| |
| /* See if the lvb has been updated */ |
| if (dlm_lvb_operations[li->li_grmode+1][li->li_rqmode+1] == 1) |
| lvb_updated = 1; |
| |
| if (li->li_lksb.sb_status == 0) |
| li->li_grmode = li->li_rqmode; |
| |
| /* Only queue AST if the device is still open */ |
| if (test_bit(FI_FLAG_OPEN, &li->li_file->fi_flags)) |
| add_to_astqueue(li, li->li_castaddr, li->li_castparam, |
| lvb_updated); |
| |
| /* If it's a new lock operation that failed, then |
| * remove it from the owner queue and free the |
| * lock_info. |
| */ |
| if (test_and_clear_bit(LI_FLAG_FIRSTLOCK, &li->li_flags) && |
| li->li_lksb.sb_status != 0) { |
| |
| /* Wait till dlm_lock() has finished */ |
| wait_for_completion(&li->li_firstcomp); |
| |
| spin_lock(&li->li_file->fi_li_lock); |
| list_del(&li->li_ownerqueue); |
| clear_bit(LI_FLAG_ONLIST, &li->li_flags); |
| spin_unlock(&li->li_file->fi_li_lock); |
| release_lockinfo(li->li_file->fi_ls, li); |
| return; |
| } |
| /* Free unlocks & queries */ |
| if (li->li_lksb.sb_status == -DLM_EUNLOCK || |
| li->li_cmd == DLM_USER_QUERY) { |
| release_lockinfo(li->li_file->fi_ls, li); |
| } |
| } else { |
| /* Synchronous request, just wake up the caller */ |
| set_bit(LI_FLAG_COMPLETE, &li->li_flags); |
| wake_up_interruptible(&li->li_waitq); |
| } |
| } |
| |
| /* |
| * Wait for the lock op to complete and return the status. |
| */ |
| static int wait_for_ast(struct lock_info *li) |
| { |
| /* Wait for the AST routine to complete */ |
| set_task_state(current, TASK_INTERRUPTIBLE); |
| while (!test_bit(LI_FLAG_COMPLETE, &li->li_flags)) |
| schedule(); |
| |
| set_task_state(current, TASK_RUNNING); |
| |
| return li->li_lksb.sb_status; |
| } |
| |
| |
| /* Open on control device */ |
| static int dlm_ctl_open(struct inode *inode, struct file *file) |
| { |
| file->private_data = NULL; |
| return 0; |
| } |
| |
| /* Close on control device */ |
| static int dlm_ctl_close(struct inode *inode, struct file *file) |
| { |
| return 0; |
| } |
| |
| /* Open on lockspace device */ |
| static int dlm_open(struct inode *inode, struct file *file) |
| { |
| struct file_info *f; |
| struct user_ls *lsinfo; |
| |
| lsinfo = find_lockspace(iminor(inode)); |
| if (!lsinfo) |
| return -ENOENT; |
| |
| f = kzalloc(sizeof(struct file_info), GFP_KERNEL); |
| if (!f) |
| return -ENOMEM; |
| |
| atomic_inc(&lsinfo->ls_refcnt); |
| INIT_LIST_HEAD(&f->fi_li_list); |
| INIT_LIST_HEAD(&f->fi_ast_list); |
| spin_lock_init(&f->fi_li_lock); |
| spin_lock_init(&f->fi_ast_lock); |
| init_waitqueue_head(&f->fi_wait); |
| f->fi_ls = lsinfo; |
| f->fi_flags = 0; |
| get_file_info(f); |
| set_bit(FI_FLAG_OPEN, &f->fi_flags); |
| |
| file->private_data = f; |
| |
| return 0; |
| } |
| |
| /* Check the user's version matches ours */ |
| static int check_version(struct dlm_write_request *req) |
| { |
| if (req->version[0] != DLM_DEVICE_VERSION_MAJOR || |
| (req->version[0] == DLM_DEVICE_VERSION_MAJOR && |
| req->version[1] > DLM_DEVICE_VERSION_MINOR)) { |
| |
| printk(KERN_DEBUG "dlm: process %s (%d) version mismatch " |
| "user (%d.%d.%d) kernel (%d.%d.%d)\n", |
| current->comm, |
| current->pid, |
| req->version[0], |
| req->version[1], |
| req->version[2], |
| DLM_DEVICE_VERSION_MAJOR, |
| DLM_DEVICE_VERSION_MINOR, |
| DLM_DEVICE_VERSION_PATCH); |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| /* Close on lockspace device */ |
| static int dlm_close(struct inode *inode, struct file *file) |
| { |
| struct file_info *f = file->private_data; |
| struct lock_info li; |
| struct lock_info *old_li, *safe; |
| sigset_t tmpsig; |
| sigset_t allsigs; |
| struct user_ls *lsinfo; |
| DECLARE_WAITQUEUE(wq, current); |
| |
| lsinfo = find_lockspace(iminor(inode)); |
| if (!lsinfo) |
| return -ENOENT; |
| |
| /* Mark this closed so that ASTs will not be delivered any more */ |
| clear_bit(FI_FLAG_OPEN, &f->fi_flags); |
| |
| /* Block signals while we are doing this */ |
| sigfillset(&allsigs); |
| sigprocmask(SIG_BLOCK, &allsigs, &tmpsig); |
| |
| /* We use our own lock_info struct here, so that any |
| * outstanding "real" ASTs will be delivered with the |
| * corresponding "real" params, thus freeing the lock_info |
| * that belongs the lock. This catches the corner case where |
| * a lock is BUSY when we try to unlock it here |
| */ |
| memset(&li, 0, sizeof(li)); |
| clear_bit(LI_FLAG_COMPLETE, &li.li_flags); |
| init_waitqueue_head(&li.li_waitq); |
| add_wait_queue(&li.li_waitq, &wq); |
| |
| /* |
| * Free any outstanding locks, they are on the |
| * list in LIFO order so there should be no problems |
| * about unlocking parents before children. |
| */ |
| list_for_each_entry_safe(old_li, safe, &f->fi_li_list, li_ownerqueue) { |
| int status; |
| int flags = 0; |
| |
| /* Don't unlock persistent locks, just mark them orphaned */ |
| if (test_bit(LI_FLAG_PERSISTENT, &old_li->li_flags)) { |
| list_del(&old_li->li_ownerqueue); |
| |
| /* Update master copy */ |
| /* TODO: Check locking core updates the local and |
| remote ORPHAN flags */ |
| li.li_lksb.sb_lkid = old_li->li_lksb.sb_lkid; |
| status = dlm_lock(f->fi_ls->ls_lockspace, |
| old_li->li_grmode, &li.li_lksb, |
| DLM_LKF_CONVERT|DLM_LKF_ORPHAN, |
| NULL, 0, 0, ast_routine, NULL, NULL); |
| if (status != 0) |
| printk("dlm: Error orphaning lock %x: %d\n", |
| old_li->li_lksb.sb_lkid, status); |
| |
| /* But tidy our references in it */ |
| release_lockinfo(old_li->li_file->fi_ls, old_li); |
| continue; |
| } |
| |
| clear_bit(LI_FLAG_COMPLETE, &li.li_flags); |
| |
| flags = DLM_LKF_FORCEUNLOCK; |
| if (old_li->li_grmode >= DLM_LOCK_PW) |
| flags |= DLM_LKF_IVVALBLK; |
| |
| status = dlm_unlock(f->fi_ls->ls_lockspace, |
| old_li->li_lksb.sb_lkid, flags, |
| &li.li_lksb, &li); |
| |
| /* Must wait for it to complete as the next lock could be its |
| * parent */ |
| if (status == 0) |
| wait_for_ast(&li); |
| |
| /* Unlock suceeded, free the lock_info struct. */ |
| if (status == 0) |
| release_lockinfo(old_li->li_file->fi_ls, old_li); |
| } |
| |
| remove_wait_queue(&li.li_waitq, &wq); |
| |
| /* |
| * If this is the last reference to the lockspace |
| * then free the struct. If it's an AUTOFREE lockspace |
| * then free the whole thing. |
| */ |
| mutex_lock(&user_ls_lock); |
| if (atomic_dec_and_test(&lsinfo->ls_refcnt)) { |
| |
| if (lsinfo->ls_lockspace) { |
| if (test_bit(LS_FLAG_AUTOFREE, &lsinfo->ls_flags)) { |
| unregister_lockspace(lsinfo, 1); |
| } |
| } else { |
| kfree(lsinfo->ls_miscinfo.name); |
| kfree(lsinfo); |
| } |
| } |
| mutex_unlock(&user_ls_lock); |
| put_file_info(f); |
| |
| /* Restore signals */ |
| sigprocmask(SIG_SETMASK, &tmpsig, NULL); |
| recalc_sigpending(); |
| |
| return 0; |
| } |
| |
| static int do_user_create_lockspace(struct file_info *fi, uint8_t cmd, |
| struct dlm_lspace_params *kparams) |
| { |
| int status; |
| struct user_ls *lsinfo; |
| |
| if (!capable(CAP_SYS_ADMIN)) |
| return -EPERM; |
| |
| status = register_lockspace(kparams->name, &lsinfo, kparams->flags); |
| |
| /* If it succeeded then return the minor number */ |
| if (status == 0) |
| status = lsinfo->ls_miscinfo.minor; |
| |
| return status; |
| } |
| |
| static int do_user_remove_lockspace(struct file_info *fi, uint8_t cmd, |
| struct dlm_lspace_params *kparams) |
| { |
| int status; |
| int force = 1; |
| struct user_ls *lsinfo; |
| |
| if (!capable(CAP_SYS_ADMIN)) |
| return -EPERM; |
| |
| mutex_lock(&user_ls_lock); |
| lsinfo = __find_lockspace(kparams->minor); |
| if (!lsinfo) { |
| mutex_unlock(&user_ls_lock); |
| return -EINVAL; |
| } |
| |
| if (kparams->flags & DLM_USER_LSFLG_FORCEFREE) |
| force = 3; |
| |
| status = unregister_lockspace(lsinfo, force); |
| mutex_unlock(&user_ls_lock); |
| |
| return status; |
| } |
| |
| /* Read call, might block if no ASTs are waiting. |
| * It will only ever return one message at a time, regardless |
| * of how many are pending. |
| */ |
| static ssize_t dlm_read(struct file *file, char __user *buffer, size_t count, |
| loff_t *ppos) |
| { |
| struct file_info *fi = file->private_data; |
| struct ast_info *ast; |
| void *data; |
| int data_size; |
| int struct_size; |
| int offset; |
| DECLARE_WAITQUEUE(wait, current); |
| #ifdef CONFIG_COMPAT |
| struct dlm_lock_result32 result32; |
| |
| if (count < sizeof(struct dlm_lock_result32)) |
| #else |
| if (count < sizeof(struct dlm_lock_result)) |
| #endif |
| return -EINVAL; |
| |
| spin_lock(&fi->fi_ast_lock); |
| if (list_empty(&fi->fi_ast_list)) { |
| |
| /* No waiting ASTs. |
| * Return EOF if the lockspace been deleted. |
| */ |
| if (test_bit(LS_FLAG_DELETED, &fi->fi_ls->ls_flags)) |
| return 0; |
| |
| if (file->f_flags & O_NONBLOCK) { |
| spin_unlock(&fi->fi_ast_lock); |
| return -EAGAIN; |
| } |
| |
| add_wait_queue(&fi->fi_wait, &wait); |
| |
| repeat: |
| set_current_state(TASK_INTERRUPTIBLE); |
| if (list_empty(&fi->fi_ast_list) && |
| !signal_pending(current)) { |
| |
| spin_unlock(&fi->fi_ast_lock); |
| schedule(); |
| spin_lock(&fi->fi_ast_lock); |
| goto repeat; |
| } |
| |
| current->state = TASK_RUNNING; |
| remove_wait_queue(&fi->fi_wait, &wait); |
| |
| if (signal_pending(current)) { |
| spin_unlock(&fi->fi_ast_lock); |
| return -ERESTARTSYS; |
| } |
| } |
| |
| ast = list_entry(fi->fi_ast_list.next, struct ast_info, list); |
| list_del(&ast->list); |
| spin_unlock(&fi->fi_ast_lock); |
| |
| /* Work out the size of the returned data */ |
| #ifdef CONFIG_COMPAT |
| if (test_bit(FI_FLAG_COMPAT, &fi->fi_flags)) { |
| data_size = struct_size = sizeof(struct dlm_lock_result32); |
| data = &result32; |
| } |
| else |
| #endif |
| { |
| data_size = struct_size = sizeof(struct dlm_lock_result); |
| data = &ast->result; |
| } |
| if (ast->lvb_updated && ast->result.lksb.sb_lvbptr) |
| data_size += DLM_USER_LVB_LEN; |
| |
| offset = struct_size; |
| |
| /* Room for the extended data ? */ |
| if (count >= data_size) { |
| |
| if (ast->lvb_updated && ast->result.lksb.sb_lvbptr) { |
| if (copy_to_user(buffer+offset, |
| ast->result.lksb.sb_lvbptr, |
| DLM_USER_LVB_LEN)) |
| return -EFAULT; |
| ast->result.lvb_offset = offset; |
| offset += DLM_USER_LVB_LEN; |
| } |
| } |
| |
| ast->result.length = data_size; |
| |
| #ifdef CONFIG_COMPAT |
| compat_output(&ast->result, &result32); |
| #endif |
| |
| /* Copy the header now it has all the offsets in it */ |
| if (copy_to_user(buffer, data, struct_size)) |
| offset = -EFAULT; |
| |
| /* If we only returned a header and there's more to come then put it |
| back on the list */ |
| if (count < data_size) { |
| spin_lock(&fi->fi_ast_lock); |
| list_add(&ast->list, &fi->fi_ast_list); |
| spin_unlock(&fi->fi_ast_lock); |
| } else |
| kfree(ast); |
| return offset; |
| } |
| |
| static unsigned int dlm_poll(struct file *file, poll_table *wait) |
| { |
| struct file_info *fi = file->private_data; |
| |
| poll_wait(file, &fi->fi_wait, wait); |
| |
| spin_lock(&fi->fi_ast_lock); |
| if (!list_empty(&fi->fi_ast_list)) { |
| spin_unlock(&fi->fi_ast_lock); |
| return POLLIN | POLLRDNORM; |
| } |
| |
| spin_unlock(&fi->fi_ast_lock); |
| return 0; |
| } |
| |
| static struct lock_info *allocate_lockinfo(struct file_info *fi, uint8_t cmd, |
| struct dlm_lock_params *kparams) |
| { |
| struct lock_info *li; |
| |
| if (!try_module_get(THIS_MODULE)) |
| return NULL; |
| |
| li = kzalloc(sizeof(struct lock_info), GFP_KERNEL); |
| if (li) { |
| li->li_magic = LOCKINFO_MAGIC; |
| li->li_file = fi; |
| li->li_cmd = cmd; |
| li->li_flags = 0; |
| li->li_grmode = -1; |
| li->li_rqmode = -1; |
| li->li_pend_bastparam = NULL; |
| li->li_pend_bastaddr = NULL; |
| li->li_castaddr = NULL; |
| li->li_castparam = NULL; |
| li->li_lksb.sb_lvbptr = NULL; |
| li->li_bastaddr = kparams->bastaddr; |
| li->li_bastparam = kparams->bastparam; |
| |
| get_file_info(fi); |
| } |
| return li; |
| } |
| |
| static int do_user_lock(struct file_info *fi, uint8_t cmd, |
| struct dlm_lock_params *kparams) |
| { |
| struct lock_info *li; |
| int status; |
| |
| /* |
| * Validate things that we need to have correct. |
| */ |
| if (!kparams->castaddr) |
| return -EINVAL; |
| |
| if (!kparams->lksb) |
| return -EINVAL; |
| |
| /* Persistent child locks are not available yet */ |
| if ((kparams->flags & DLM_LKF_PERSISTENT) && kparams->parent) |
| return -EINVAL; |
| |
| /* For conversions, there should already be a lockinfo struct, |
| unless we are adopting an orphaned persistent lock */ |
| if (kparams->flags & DLM_LKF_CONVERT) { |
| |
| li = get_lockinfo(fi->fi_ls, kparams->lkid); |
| |
| /* If this is a persistent lock we will have to create a |
| lockinfo again */ |
| if (!li && (kparams->flags & DLM_LKF_PERSISTENT)) { |
| li = allocate_lockinfo(fi, cmd, kparams); |
| if (!li) |
| return -ENOMEM; |
| |
| li->li_lksb.sb_lkid = kparams->lkid; |
| li->li_castaddr = kparams->castaddr; |
| li->li_castparam = kparams->castparam; |
| |
| /* OK, this isn't exactly a FIRSTLOCK but it is the |
| first time we've used this lockinfo, and if things |
| fail we want rid of it */ |
| init_completion(&li->li_firstcomp); |
| set_bit(LI_FLAG_FIRSTLOCK, &li->li_flags); |
| add_lockinfo(fi->fi_ls, li); |
| |
| /* TODO: do a query to get the current state ?? */ |
| } |
| if (!li) |
| return -EINVAL; |
| |
| if (li->li_magic != LOCKINFO_MAGIC) |
| return -EINVAL; |
| |
| /* For conversions don't overwrite the current blocking AST |
| info so that: |
| a) if a blocking AST fires before the conversion is queued |
| it runs the current handler |
| b) if the conversion is cancelled, the original blocking AST |
| declaration is active |
| The pend_ info is made active when the conversion |
| completes. |
| */ |
| li->li_pend_bastaddr = kparams->bastaddr; |
| li->li_pend_bastparam = kparams->bastparam; |
| } else { |
| li = allocate_lockinfo(fi, cmd, kparams); |
| if (!li) |
| return -ENOMEM; |
| |
| /* Allow us to complete our work before |
| the AST routine runs. In fact we only need (and use) this |
| when the initial lock fails */ |
| init_completion(&li->li_firstcomp); |
| set_bit(LI_FLAG_FIRSTLOCK, &li->li_flags); |
| } |
| |
| li->li_user_lksb = kparams->lksb; |
| li->li_castaddr = kparams->castaddr; |
| li->li_castparam = kparams->castparam; |
| li->li_lksb.sb_lkid = kparams->lkid; |
| li->li_rqmode = kparams->mode; |
| if (kparams->flags & DLM_LKF_PERSISTENT) |
| set_bit(LI_FLAG_PERSISTENT, &li->li_flags); |
| |
| /* Copy in the value block */ |
| if (kparams->flags & DLM_LKF_VALBLK) { |
| if (!li->li_lksb.sb_lvbptr) { |
| li->li_lksb.sb_lvbptr = kmalloc(DLM_USER_LVB_LEN, |
| GFP_KERNEL); |
| if (!li->li_lksb.sb_lvbptr) { |
| status = -ENOMEM; |
| goto out_err; |
| } |
| } |
| |
| memcpy(li->li_lksb.sb_lvbptr, kparams->lvb, DLM_USER_LVB_LEN); |
| } |
| |
| /* Lock it ... */ |
| status = dlm_lock(fi->fi_ls->ls_lockspace, |
| kparams->mode, &li->li_lksb, |
| kparams->flags, |
| kparams->name, kparams->namelen, |
| kparams->parent, |
| ast_routine, |
| li, |
| (li->li_pend_bastaddr || li->li_bastaddr) ? |
| bast_routine : NULL); |
| if (status) |
| goto out_err; |
| |
| /* If it succeeded (this far) with a new lock then keep track of |
| it on the file's lockinfo list */ |
| if (!status && test_bit(LI_FLAG_FIRSTLOCK, &li->li_flags)) { |
| |
| spin_lock(&fi->fi_li_lock); |
| list_add(&li->li_ownerqueue, &fi->fi_li_list); |
| set_bit(LI_FLAG_ONLIST, &li->li_flags); |
| spin_unlock(&fi->fi_li_lock); |
| if (add_lockinfo(fi->fi_ls, li)) |
| printk(KERN_WARNING "Add lockinfo failed\n"); |
| |
| complete(&li->li_firstcomp); |
| } |
| |
| /* Return the lockid as the user needs it /now/ */ |
| return li->li_lksb.sb_lkid; |
| |
| out_err: |
| if (test_bit(LI_FLAG_FIRSTLOCK, &li->li_flags)) |
| release_lockinfo(fi->fi_ls, li); |
| return status; |
| |
| } |
| |
| static int do_user_unlock(struct file_info *fi, uint8_t cmd, |
| struct dlm_lock_params *kparams) |
| { |
| struct lock_info *li; |
| int status; |
| int convert_cancel = 0; |
| |
| li = get_lockinfo(fi->fi_ls, kparams->lkid); |
| if (!li) { |
| li = allocate_lockinfo(fi, cmd, kparams); |
| if (!li) |
| return -ENOMEM; |
| spin_lock(&fi->fi_li_lock); |
| list_add(&li->li_ownerqueue, &fi->fi_li_list); |
| set_bit(LI_FLAG_ONLIST, &li->li_flags); |
| spin_unlock(&fi->fi_li_lock); |
| } |
| |
| if (li->li_magic != LOCKINFO_MAGIC) |
| return -EINVAL; |
| |
| li->li_user_lksb = kparams->lksb; |
| li->li_castparam = kparams->castparam; |
| li->li_cmd = cmd; |
| |
| /* Cancelling a conversion doesn't remove the lock...*/ |
| if (kparams->flags & DLM_LKF_CANCEL && li->li_grmode != -1) |
| convert_cancel = 1; |
| |
| /* Wait until dlm_lock() has completed */ |
| if (!test_bit(LI_FLAG_ONLIST, &li->li_flags)) { |
| wait_for_completion(&li->li_firstcomp); |
| } |
| |
| /* dlm_unlock() passes a 0 for castaddr which means don't overwrite |
| the existing li_castaddr as that's the completion routine for |
| unlocks. dlm_unlock_wait() specifies a new AST routine to be |
| executed when the unlock completes. */ |
| if (kparams->castaddr) |
| li->li_castaddr = kparams->castaddr; |
| |
| /* Use existing lksb & astparams */ |
| status = dlm_unlock(fi->fi_ls->ls_lockspace, |
| kparams->lkid, |
| kparams->flags, &li->li_lksb, li); |
| |
| if (!status && !convert_cancel) { |
| spin_lock(&fi->fi_li_lock); |
| list_del(&li->li_ownerqueue); |
| clear_bit(LI_FLAG_ONLIST, &li->li_flags); |
| spin_unlock(&fi->fi_li_lock); |
| } |
| |
| return status; |
| } |
| |
| /* Write call, submit a locking request */ |
| static ssize_t dlm_write(struct file *file, const char __user *buffer, |
| size_t count, loff_t *ppos) |
| { |
| struct file_info *fi = file->private_data; |
| struct dlm_write_request *kparams; |
| sigset_t tmpsig; |
| sigset_t allsigs; |
| int status; |
| |
| #ifdef CONFIG_COMPAT |
| if (count < sizeof(struct dlm_write_request32)) |
| #else |
| if (count < sizeof(struct dlm_write_request)) |
| #endif |
| return -EINVAL; |
| |
| if (count > sizeof(struct dlm_write_request) + DLM_RESNAME_MAXLEN) |
| return -EINVAL; |
| |
| /* Has the lockspace been deleted */ |
| if (fi && test_bit(LS_FLAG_DELETED, &fi->fi_ls->ls_flags)) |
| return -ENOENT; |
| |
| kparams = kmalloc(count, GFP_KERNEL); |
| if (!kparams) |
| return -ENOMEM; |
| |
| status = -EFAULT; |
| /* Get the command info */ |
| if (copy_from_user(kparams, buffer, count)) |
| goto out_free; |
| |
| status = -EBADE; |
| if (check_version(kparams)) |
| goto out_free; |
| |
| #ifdef CONFIG_COMPAT |
| if (!kparams->is64bit) { |
| struct dlm_write_request32 *k32params = (struct dlm_write_request32 *)kparams; |
| kparams = kmalloc(count + (sizeof(struct dlm_write_request) - sizeof(struct dlm_write_request32)), GFP_KERNEL); |
| if (!kparams) |
| return -ENOMEM; |
| |
| if (fi) |
| set_bit(FI_FLAG_COMPAT, &fi->fi_flags); |
| compat_input(kparams, k32params); |
| kfree(k32params); |
| } |
| #endif |
| |
| /* Block signals while we are doing this */ |
| sigfillset(&allsigs); |
| sigprocmask(SIG_BLOCK, &allsigs, &tmpsig); |
| |
| status = -EINVAL; |
| switch (kparams->cmd) |
| { |
| case DLM_USER_LOCK: |
| if (!fi) goto out_sig; |
| status = do_user_lock(fi, kparams->cmd, &kparams->i.lock); |
| break; |
| |
| case DLM_USER_UNLOCK: |
| if (!fi) goto out_sig; |
| status = do_user_unlock(fi, kparams->cmd, &kparams->i.lock); |
| break; |
| |
| case DLM_USER_CREATE_LOCKSPACE: |
| if (fi) goto out_sig; |
| status = do_user_create_lockspace(fi, kparams->cmd, |
| &kparams->i.lspace); |
| break; |
| |
| case DLM_USER_REMOVE_LOCKSPACE: |
| if (fi) goto out_sig; |
| status = do_user_remove_lockspace(fi, kparams->cmd, |
| &kparams->i.lspace); |
| break; |
| default: |
| printk("Unknown command passed to DLM device : %d\n", |
| kparams->cmd); |
| break; |
| } |
| |
| out_sig: |
| /* Restore signals */ |
| sigprocmask(SIG_SETMASK, &tmpsig, NULL); |
| recalc_sigpending(); |
| |
| out_free: |
| kfree(kparams); |
| if (status == 0) |
| return count; |
| else |
| return status; |
| } |
| |
| static struct file_operations _dlm_fops = { |
| .open = dlm_open, |
| .release = dlm_close, |
| .read = dlm_read, |
| .write = dlm_write, |
| .poll = dlm_poll, |
| .owner = THIS_MODULE, |
| }; |
| |
| static struct file_operations _dlm_ctl_fops = { |
| .open = dlm_ctl_open, |
| .release = dlm_ctl_close, |
| .write = dlm_write, |
| .owner = THIS_MODULE, |
| }; |
| |
| /* |
| * Create control device |
| */ |
| static int __init dlm_device_init(void) |
| { |
| int r; |
| |
| INIT_LIST_HEAD(&user_ls_list); |
| mutex_init(&user_ls_lock); |
| |
| ctl_device.name = "dlm-control"; |
| ctl_device.fops = &_dlm_ctl_fops; |
| ctl_device.minor = MISC_DYNAMIC_MINOR; |
| |
| r = misc_register(&ctl_device); |
| if (r) { |
| printk(KERN_ERR "dlm: misc_register failed for control dev\n"); |
| return r; |
| } |
| |
| return 0; |
| } |
| |
| static void __exit dlm_device_exit(void) |
| { |
| misc_deregister(&ctl_device); |
| } |
| |
| MODULE_DESCRIPTION("Distributed Lock Manager device interface"); |
| MODULE_AUTHOR("Red Hat, Inc."); |
| MODULE_LICENSE("GPL"); |
| |
| module_init(dlm_device_init); |
| module_exit(dlm_device_exit); |