linux-stable/fs/gfs2/locking/dlm/plock.c
David Teigland 08eac93a68 [GFS2] match plock result with correct request
When the result of a posix lock request is read it needs to be matched up
with the correct waiting request.  The owner field needs to be used in the
comparison since more than one process may be waiting for locks on the
same file.

Signed-off-by: David Teigland <teigland@redhat.com>
Signed-off-by: Steven Whitehouse <swhiteho@redhat.com>
2006-08-07 08:46:51 -04:00

302 lines
6.7 KiB
C

/*
* Copyright (C) 2005 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.
*/
#include <linux/miscdevice.h>
#include <linux/lock_dlm_plock.h>
#include "lock_dlm.h"
static spinlock_t ops_lock;
static struct list_head send_list;
static struct list_head recv_list;
static wait_queue_head_t send_wq;
static wait_queue_head_t recv_wq;
struct plock_op {
struct list_head list;
int done;
struct gdlm_plock_info info;
};
static inline void set_version(struct gdlm_plock_info *info)
{
info->version[0] = GDLM_PLOCK_VERSION_MAJOR;
info->version[1] = GDLM_PLOCK_VERSION_MINOR;
info->version[2] = GDLM_PLOCK_VERSION_PATCH;
}
static int check_version(struct gdlm_plock_info *info)
{
if ((GDLM_PLOCK_VERSION_MAJOR != info->version[0]) ||
(GDLM_PLOCK_VERSION_MINOR < info->version[1])) {
log_error("plock device version mismatch: "
"kernel (%u.%u.%u), user (%u.%u.%u)",
GDLM_PLOCK_VERSION_MAJOR,
GDLM_PLOCK_VERSION_MINOR,
GDLM_PLOCK_VERSION_PATCH,
info->version[0],
info->version[1],
info->version[2]);
return -EINVAL;
}
return 0;
}
static void send_op(struct plock_op *op)
{
set_version(&op->info);
INIT_LIST_HEAD(&op->list);
spin_lock(&ops_lock);
list_add_tail(&op->list, &send_list);
spin_unlock(&ops_lock);
wake_up(&send_wq);
}
int gdlm_plock(lm_lockspace_t *lockspace, struct lm_lockname *name,
struct file *file, int cmd, struct file_lock *fl)
{
struct gdlm_ls *ls = (struct gdlm_ls *) lockspace;
struct plock_op *op;
int rv;
op = kzalloc(sizeof(*op), GFP_KERNEL);
if (!op)
return -ENOMEM;
op->info.optype = GDLM_PLOCK_OP_LOCK;
op->info.pid = fl->fl_pid;
op->info.ex = (fl->fl_type == F_WRLCK);
op->info.wait = IS_SETLKW(cmd);
op->info.fsid = ls->id;
op->info.number = name->ln_number;
op->info.start = fl->fl_start;
op->info.end = fl->fl_end;
op->info.owner = (__u64)(long) fl->fl_owner;
send_op(op);
wait_event(recv_wq, (op->done != 0));
spin_lock(&ops_lock);
if (!list_empty(&op->list)) {
printk(KERN_INFO "plock op on list\n");
list_del(&op->list);
}
spin_unlock(&ops_lock);
rv = op->info.rv;
if (!rv) {
if (posix_lock_file_wait(file, fl) < 0)
log_error("gdlm_plock: vfs lock error %x,%llx",
name->ln_type,
(unsigned long long)name->ln_number);
}
kfree(op);
return rv;
}
int gdlm_punlock(lm_lockspace_t *lockspace, struct lm_lockname *name,
struct file *file, struct file_lock *fl)
{
struct gdlm_ls *ls = (struct gdlm_ls *) lockspace;
struct plock_op *op;
int rv;
op = kzalloc(sizeof(*op), GFP_KERNEL);
if (!op)
return -ENOMEM;
if (posix_lock_file_wait(file, fl) < 0)
log_error("gdlm_punlock: vfs unlock error %x,%llx",
name->ln_type, (unsigned long long)name->ln_number);
op->info.optype = GDLM_PLOCK_OP_UNLOCK;
op->info.pid = fl->fl_pid;
op->info.fsid = ls->id;
op->info.number = name->ln_number;
op->info.start = fl->fl_start;
op->info.end = fl->fl_end;
op->info.owner = (__u64)(long) fl->fl_owner;
send_op(op);
wait_event(recv_wq, (op->done != 0));
spin_lock(&ops_lock);
if (!list_empty(&op->list)) {
printk(KERN_INFO "punlock op on list\n");
list_del(&op->list);
}
spin_unlock(&ops_lock);
rv = op->info.rv;
kfree(op);
return rv;
}
int gdlm_plock_get(lm_lockspace_t *lockspace, struct lm_lockname *name,
struct file *file, struct file_lock *fl)
{
struct gdlm_ls *ls = (struct gdlm_ls *) lockspace;
struct plock_op *op;
int rv;
op = kzalloc(sizeof(*op), GFP_KERNEL);
if (!op)
return -ENOMEM;
op->info.optype = GDLM_PLOCK_OP_GET;
op->info.pid = fl->fl_pid;
op->info.ex = (fl->fl_type == F_WRLCK);
op->info.fsid = ls->id;
op->info.number = name->ln_number;
op->info.start = fl->fl_start;
op->info.end = fl->fl_end;
send_op(op);
wait_event(recv_wq, (op->done != 0));
spin_lock(&ops_lock);
if (!list_empty(&op->list)) {
printk(KERN_INFO "plock_get op on list\n");
list_del(&op->list);
}
spin_unlock(&ops_lock);
rv = op->info.rv;
if (rv == 0)
fl->fl_type = F_UNLCK;
else if (rv > 0) {
fl->fl_type = (op->info.ex) ? F_WRLCK : F_RDLCK;
fl->fl_pid = op->info.pid;
fl->fl_start = op->info.start;
fl->fl_end = op->info.end;
}
kfree(op);
return rv;
}
/* a read copies out one plock request from the send list */
static ssize_t dev_read(struct file *file, char __user *u, size_t count,
loff_t *ppos)
{
struct gdlm_plock_info info;
struct plock_op *op = NULL;
if (count < sizeof(info))
return -EINVAL;
spin_lock(&ops_lock);
if (!list_empty(&send_list)) {
op = list_entry(send_list.next, struct plock_op, list);
list_move(&op->list, &recv_list);
memcpy(&info, &op->info, sizeof(info));
}
spin_unlock(&ops_lock);
if (!op)
return -EAGAIN;
if (copy_to_user(u, &info, sizeof(info)))
return -EFAULT;
return sizeof(info);
}
/* a write copies in one plock result that should match a plock_op
on the recv list */
static ssize_t dev_write(struct file *file, const char __user *u, size_t count,
loff_t *ppos)
{
struct gdlm_plock_info info;
struct plock_op *op;
int found = 0;
if (count != sizeof(info))
return -EINVAL;
if (copy_from_user(&info, u, sizeof(info)))
return -EFAULT;
if (check_version(&info))
return -EINVAL;
spin_lock(&ops_lock);
list_for_each_entry(op, &recv_list, list) {
if (op->info.fsid == info.fsid &&
op->info.number == info.number &&
op->info.owner == info.owner) {
list_del_init(&op->list);
found = 1;
op->done = 1;
memcpy(&op->info, &info, sizeof(info));
break;
}
}
spin_unlock(&ops_lock);
if (found)
wake_up(&recv_wq);
else
printk(KERN_INFO "gdlm dev_write no op %x %llx\n", info.fsid,
(unsigned long long)info.number);
return count;
}
static unsigned int dev_poll(struct file *file, poll_table *wait)
{
poll_wait(file, &send_wq, wait);
spin_lock(&ops_lock);
if (!list_empty(&send_list)) {
spin_unlock(&ops_lock);
return POLLIN | POLLRDNORM;
}
spin_unlock(&ops_lock);
return 0;
}
static struct file_operations dev_fops = {
.read = dev_read,
.write = dev_write,
.poll = dev_poll,
.owner = THIS_MODULE
};
static struct miscdevice plock_dev_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = GDLM_PLOCK_MISC_NAME,
.fops = &dev_fops
};
int gdlm_plock_init(void)
{
int rv;
spin_lock_init(&ops_lock);
INIT_LIST_HEAD(&send_list);
INIT_LIST_HEAD(&recv_list);
init_waitqueue_head(&send_wq);
init_waitqueue_head(&recv_wq);
rv = misc_register(&plock_dev_misc);
if (rv)
printk(KERN_INFO "gdlm_plock_init: misc_register failed %d",
rv);
return rv;
}
void gdlm_plock_exit(void)
{
if (misc_deregister(&plock_dev_misc) < 0)
printk(KERN_INFO "gdlm_plock_exit: misc_deregister failed");
}