android_kernel_xiaomi_sm8350/fs/dlm/device.c
Patrick Caulfield 9f5aa2a921 [DLM] Fix potential conflict in DLM userland locks
Just spotted this one. The lockinfo structs are hashed by lockid but into a
global structure. So that if there are two lockspaces with the same lockid all
hell breaks loose. I'm not exactly sure what will happen but it can't be good!

The attached patch moves the lockinfo_idr into the user_ls structure so that
lockids are localised.

patrick

Signed-Off-By: Patrick Caulfield <pcaulfie@redhat.com>
Signed-off-by: Steven Whitehouse <swhiteho@redhat.com>
2006-06-19 16:27:52 -04:00

1240 lines
30 KiB
C

/******************************************************************************
*******************************************************************************
**
** 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);