/* * 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 #include #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) { 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"); }