Logo Search packages:      
Sourcecode: redhat-cluster-suite version File versions  Download package

device.c

/******************************************************************************
*******************************************************************************
**
**  Copyright (C) Sistina Software, Inc.  1997-2003  All rights reserved.
**  Copyright (C) 2004 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 <asm/ioctls.h>

#include "dlm_internal.h"
#include "device.h"

extern struct dlm_lkb *dlm_get_lkb(struct dlm_ls *, int);
static struct file_operations _dlm_fops;
static const char *name_prefix="dlm";
static struct list_head user_ls_list;
static struct semaphore user_ls_lock;

/* Flags in li_flags */
#define LI_FLAG_COMPLETE  1
#define LI_FLAG_FIRSTLOCK 2
#define LI_FLAG_ONLIST    3

/* flags in ls_flags */
#define LS_FLAG_DELETED   1
#define LS_FLAG_AUTOFREE  2
#define LS_FLAG_DEFAULT   3


#define LOCKINFO_MAGIC 0x53595324

struct lock_info {
      uint32_t li_magic;
      uint8_t li_cmd;
      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 semaphore li_firstlock;
      struct dlm_queryinfo *li_queryinfo;
      struct dlm_queryinfo __user *li_user_queryinfo;
};

/* A queued AST no less */
struct ast_info {
      struct dlm_lock_result result;
      struct dlm_queryinfo *queryinfo;
      struct dlm_queryinfo __user *user_queryinfo;
      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;

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

/* Bit 1 of this is set when we have a default ls */
static long default_ls;

/*
 * 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_lkb_list;     /* List of active lkbs */
      spinlock_t          fi_lkb_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;        /* Bit 1 means the device is open */
};


/* 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 lock_info *li)
{
      put_file_info(li->li_file);
      if (li->li_lksb.sb_lvbptr)
            kfree(li->li_lksb.sb_lvbptr);
      kfree(li);
}

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;

      down(&user_ls_lock);
      lsinfo = __find_lockspace(minor);
      up(&user_ls_lock);

      return lsinfo;
}

static void add_lockspace_to_list(struct user_ls *lsinfo)
{
      down(&user_ls_lock);
      list_add(&lsinfo->ls_list, &user_ls_list);
      up(&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 = kmalloc(sizeof(struct user_ls), GFP_KERNEL);
      if (!newls)
            return -ENOMEM;
      memset(newls, 0, sizeof(struct user_ls));

      newls->ls_miscinfo.name = kmalloc(namelen, GFP_KERNEL);
      if (!newls->ls_miscinfo.name) {
            kfree(newls);
            return -ENOMEM;
      }
      status = dlm_new_lockspace(name, strlen(name),
                           &newls->ls_lockspace, 0);

      if (status != 0) {
            kfree(newls->ls_miscinfo.name);
            kfree(newls);
            return status;
      }

      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) {
            log_print("failed to register misc device for %s", 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);
      if (flags & DLM_USER_LSFLG_DEFAULTLS)
            set_bit(LS_FLAG_DEFAULT, &newls->ls_flags);

      add_lockspace_to_list(newls);
      *ls = newls;
      return 0;
}

/* Called with the user_ls_lock semaphore held */
static int unregister_lockspace(struct user_ls *lsinfo, int force)
{
      int status = 0;

      if (atomic_read(&lsinfo->ls_refcnt) == 0) {
            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 (test_bit(LS_FLAG_DEFAULT, &lsinfo->ls_flags))
                  clear_bit(1, &default_ls);


            kfree(lsinfo->ls_miscinfo.name);
            kfree(lsinfo);
      }
      else {
            status = -EBUSY;
      }

      return status;
}

/* 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 = kmalloc(sizeof(struct ast_info), GFP_KERNEL);
      if (!ast)
            return;

      memset(ast, 0, sizeof(*ast));
      ast->result.user_astparam = astparam;
      ast->result.user_astaddr  = astaddr;
      ast->result.user_lksb     = li->li_user_lksb;
      ast->result.user_qinfo    = li->li_user_queryinfo;
      memcpy(&ast->result.lksb, &li->li_lksb, sizeof(struct dlm_lksb));

      ast->queryinfo   = li->li_queryinfo;
      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;
            struct dlm_lkb *lkb;

            /* See if the lvb has been updated */
            lkb = dlm_get_lkb(li->li_file->fi_ls->ls_lockspace, li->li_lksb.sb_lkid);
            if (lkb && lkb->lkb_flags & GDLM_LKFLG_RETURNLVB)
                  lvb_updated = 1;

            /* Only queue AST if the device is still open */
            if (test_bit(1, &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. The DLM will not free the LKB until this
             * AST has completed.
             */
            if (test_and_clear_bit(LI_FLAG_FIRSTLOCK, &li->li_flags) &&
                li->li_lksb.sb_status != 0) {
                  struct dlm_lkb *lkb;

                  /* Wait till dlm_lock() has finished */
                  down(&li->li_firstlock);
                  up(&li->li_firstlock);

                  /* If the LKB has been freed then we need to tidy up too */
                  lkb = dlm_get_lkb(li->li_file->fi_ls->ls_lockspace, li->li_lksb.sb_lkid);
                  if (!lkb) {
                        spin_lock(&li->li_file->fi_lkb_lock);
                        list_del(&li->li_ownerqueue);
                        clear_bit(LI_FLAG_ONLIST, &li->li_flags);
                        spin_unlock(&li->li_file->fi_lkb_lock);
                        release_lockinfo(li);
                  }
                  return;
            }
            /* Free unlocks & queries */
            if ((li->li_lksb.sb_status == -DLM_EUNLOCK && !lkb) ||
                li->li_cmd == DLM_USER_QUERY) {
                  release_lockinfo(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 = kmalloc(sizeof(struct file_info), GFP_KERNEL);
      if (!f)
            return -ENOMEM;

      atomic_inc(&lsinfo->ls_refcnt);
      INIT_LIST_HEAD(&f->fi_lkb_list);
      INIT_LIST_HEAD(&f->fi_ast_list);
      spin_lock_init(&f->fi_ast_lock);
      spin_lock_init(&f->fi_lkb_lock);
      init_waitqueue_head(&f->fi_wait);
      f->fi_ls = lsinfo;
      atomic_set(&f->fi_refcnt, 1);
      f->fi_flags = 0;
      set_bit(1, &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)) {

            log_print("version mismatch user (%d.%d.%d) kernel (%d.%d.%d)",
                   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(1, &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.
       * Although we don't remove the lkbs from the list here
       * (what would be the point?), foreach_safe is needed
       * because the lkbs are freed during dlm_unlock operations
       */
      list_for_each_entry_safe(old_li, safe, &f->fi_lkb_list, li_ownerqueue) {
            int status;
            int lock_status;
            int flags = 0;
            struct dlm_lkb *lkb;

            lkb = dlm_get_lkb(f->fi_ls->ls_lockspace, old_li->li_lksb.sb_lkid);

            /* Don't unlock persistent locks */
            if (lkb && lkb->lkb_flags & GDLM_LKFLG_PERSISTENT) {
                  list_del(&old_li->li_ownerqueue);

                  /* Update master copy */
                  if (lkb->lkb_resource->res_nodeid) {
                        li.li_lksb.sb_lkid = lkb->lkb_id;
                        status = dlm_lock(f->fi_ls->ls_lockspace,
                                    lkb->lkb_grmode, &li.li_lksb,
                                    DLM_LKF_CONVERT|DLM_LKF_ORPHAN,
                                    NULL, 0, 0, ast_routine, &li,
                                    NULL, NULL);
                        if (status == 0)
                              wait_for_ast(&li);
                  }
                  lkb->lkb_flags |= GDLM_LKFLG_ORPHAN;

                  /* But tidy our references in it */
                  kfree(old_li);
                  lkb->lkb_astparam = (long)NULL;
                  put_file_info(f);

                  continue;
            }

            clear_bit(LI_FLAG_COMPLETE, &li.li_flags);

            /* If it's not granted then cancel the request.
             * If the lock was WAITING then it will be dropped,
             *    if it was converting then it will be reverted to GRANTED,
             *    then we will unlock it.
             */
            lock_status = lkb->lkb_status;

            if (lock_status != GDLM_LKSTS_GRANTED)
                  flags = DLM_LKF_CANCEL;

            if (lkb->lkb_grmode >= DLM_LOCK_PW)
                  flags |= DLM_LKF_IVVALBLK;

            status = dlm_unlock(f->fi_ls->ls_lockspace, lkb->lkb_id, 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);

            /* If it was waiting for a conversion, it will
               now be granted so we can unlock it properly */
            if (lock_status == GDLM_LKSTS_CONVERT) {
                  flags &= ~DLM_LKF_CANCEL;
                  clear_bit(LI_FLAG_COMPLETE, &li.li_flags);
                  status = dlm_unlock(f->fi_ls->ls_lockspace, lkb->lkb_id, flags, &li.li_lksb, &li);

                  if (status == 0)
                        wait_for_ast(&li);
            }
            /* Unlock suceeded, free the lock_info struct. */
            if (status == 0) {
                  kfree(old_li);
                  put_file_info(f);
            }
      }

      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.
       */
      down(&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);
            }
      }
      up(&user_ls_lock);
      put_file_info(f);

      /* Restore signals */
      sigprocmask(SIG_SETMASK, &tmpsig, NULL);
      recalc_sigpending();

      return 0;
}

/*
 * ioctls to create/remove lockspaces, and check how many
 * outstanding ASTs there are against a particular LS.
 */
static int dlm_ioctl(struct inode *inode, struct file *file,
                 uint command, ulong u)
{
      struct file_info *fi = file->private_data;
      int status = -EINVAL;
      int count;
      struct list_head *tmp_list;

      switch (command) {

            /* Are there any ASTs for us to read?
             * Warning, this returns the number of messages (ASTs)
             * in the queue, NOT the number of bytes to read
             */
      case FIONREAD:
            count = 0;
            spin_lock(&fi->fi_ast_lock);
            list_for_each(tmp_list, &fi->fi_ast_list)
                  count++;
            spin_unlock(&fi->fi_ast_lock);
            status = put_user(count, (int *)u);
            break;

      default:
            return -ENOTTY;
      }

      return status;
}

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) && !(kparams->flags & DLM_USER_LSFLG_DEFAULTLS))
            return -EPERM;

      if (kparams->flags & DLM_USER_LSFLG_DEFAULTLS && test_and_set_bit(1, &default_ls))
            return -EEXIST;

      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;

      down(&user_ls_lock);
      lsinfo = __find_lockspace(kparams->minor);
      if (!lsinfo) {
            up(&user_ls_lock);
            return -EINVAL;
      }

      if (kparams->flags & DLM_USER_LSFLG_FORCEFREE)
            force = 2;

      status = unregister_lockspace(lsinfo, force);
      up(&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;
      int data_size;
      int offset;
      DECLARE_WAITQUEUE(wait, current);

      if (count < sizeof(struct dlm_lock_result))
            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 */
      data_size = sizeof(struct dlm_lock_result);
      if (ast->lvb_updated && ast->result.lksb.sb_lvbptr)
            data_size += DLM_LVB_LEN;
      if (ast->queryinfo) {
            data_size += sizeof(struct dlm_queryinfo);
            if (ast->queryinfo->gqi_resinfo)
                  data_size += sizeof(struct dlm_resinfo);
            if (ast->queryinfo->gqi_lockinfo)
                  data_size += sizeof(struct dlm_lockinfo) * ast->queryinfo->gqi_lockcount;
      }

      offset = sizeof(struct dlm_lock_result);

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

                  ast->result.lvb_offset = offset;
                  offset += DLM_LVB_LEN;
            }
            if (ast->queryinfo) {
                  if (copy_to_user(buffer+offset, ast->queryinfo, sizeof(struct dlm_queryinfo)))
                        return -EFAULT;
                  ast->result.qinfo_offset = offset;
                  offset += sizeof(struct dlm_queryinfo);

                  if (ast->queryinfo->gqi_resinfo) {
                        if (copy_to_user(buffer+offset, ast->queryinfo->gqi_resinfo, sizeof(struct dlm_resinfo)))
                              return -EFAULT;
                        ast->result.qresinfo_offset = offset;
                        offset += sizeof(struct dlm_resinfo);
                        kfree(ast->queryinfo->gqi_resinfo);
                  }

                  if (ast->queryinfo->gqi_lockinfo) {
                        if (copy_to_user(buffer+offset, ast->queryinfo->gqi_lockinfo,
                                     sizeof(struct dlm_lockinfo) * ast->queryinfo->gqi_lockcount))
                              return -EFAULT;
                        ast->result.qlockinfo_offset = offset;
                        offset += sizeof(struct dlm_lockinfo) * ast->queryinfo->gqi_lockcount;
                        kfree(ast->queryinfo->gqi_lockinfo);
                  }

                  kfree(ast->queryinfo);
            }
      }

      ast->result.length = data_size;
      /* Copy the header now it has all the offsets in it */
      if (copy_to_user(buffer, &ast->result, sizeof(struct dlm_lock_result)))
            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 int do_user_query(struct file_info *fi, uint8_t cmd, struct dlm_query_params *kparams)
{
      struct lock_info *li;
      int status;

      if (!kparams->castaddr)
            return -EINVAL;

      if (!kparams->lksb)
            return -EINVAL;

      li = kmalloc(sizeof(struct lock_info), GFP_KERNEL);
      if (!li)
            return -ENOMEM;

      get_file_info(fi);
      li->li_user_lksb = kparams->lksb;
      li->li_castparam = kparams->castparam;
      li->li_castaddr  = kparams->castaddr;
      li->li_file      = fi;
      li->li_flags     = 0;
      li->li_cmd       = cmd;
      clear_bit(LI_FLAG_FIRSTLOCK, &li->li_flags);

      li->li_user_queryinfo = kparams->qinfo;
      li->li_lksb.sb_lkid   = kparams->lkid;
      li->li_lksb.sb_lvbptr = NULL;

      /* Allocate kernel buffers for query results */
      status = -ENOMEM;
      li->li_queryinfo = kmalloc(sizeof(struct dlm_queryinfo), GFP_KERNEL);
      if (!li->li_queryinfo)
            goto out1;

      memset(li->li_queryinfo, 0, sizeof(struct dlm_queryinfo));
      li->li_queryinfo->gqi_locksize = kparams->lockinfo_max;

      if (kparams->resinfo) {
            li->li_queryinfo->gqi_resinfo = kmalloc(sizeof(struct dlm_resinfo), GFP_KERNEL);
            if (!li->li_queryinfo->gqi_resinfo)
                  goto out1;
      }
      if (kparams->lockinfo) {
            li->li_queryinfo->gqi_locksize = kparams->lockinfo_max;
            li->li_queryinfo->gqi_lockinfo =
                  kmalloc(sizeof(struct dlm_lockinfo) * kparams->lockinfo_max,
                        GFP_KERNEL);
            if (!li->li_queryinfo->gqi_lockinfo)
                  goto out2;
      }

      return dlm_query(fi->fi_ls->ls_lockspace, &li->li_lksb,
                    kparams->query,
                    li->li_queryinfo,
                    ast_routine, li);

 out2:
      kfree(li->li_queryinfo);

 out1:
      kfree(li);
      return status;
}

static struct lock_info *allocate_lockinfo(struct file_info *fi, uint8_t cmd,
                                 struct dlm_lock_params *kparams)
{
      struct lock_info *li;

      li = kmalloc(sizeof(struct lock_info), GFP_KERNEL);
      if (li) {
            li->li_magic     = LOCKINFO_MAGIC;
            li->li_file      = fi;
            li->li_cmd       = cmd;
            li->li_queryinfo = NULL;
            li->li_flags     = 0;
            li->li_pend_bastparam = NULL;
            li->li_pend_bastaddr  = 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, the lock will already have a lock_info
         block squirelled away in astparam */
      if (kparams->flags & DLM_LKF_CONVERT) {
            struct dlm_lkb *lkb = dlm_get_lkb(fi->fi_ls->ls_lockspace, kparams->lkid);
            if (!lkb) {
                  return -EINVAL;
            }

            li = (struct lock_info *)lkb->lkb_astparam;

            /* li may be NULL if the lock was persistent and the process went
               away, so we need to allocate a new one */
            if (!li) {
                  li = allocate_lockinfo(fi, cmd, kparams);
                  if (li) {
                        spin_lock(&fi->fi_lkb_lock);
                        list_add(&li->li_ownerqueue, &fi->fi_lkb_list);
                        set_bit(LI_FLAG_ONLIST, &li->li_flags);
                        spin_unlock(&fi->fi_lkb_lock);
                  }
                  else {
                        return -ENOMEM;
                  }
            }

            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;

            /* semaphore to 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_MUTEX_LOCKED(&li->li_firstlock);
            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;

      /* Copy in the value block */
      if (kparams->flags & DLM_LKF_VALBLK) {
            if (!li->li_lksb.sb_lvbptr) {
                  li->li_lksb.sb_lvbptr = kmalloc(DLM_LVB_LEN, GFP_KERNEL);
                  if (!li->li_lksb.sb_lvbptr) {
                        status = -ENOMEM;
                        goto out_err;
                  }
            }

            memcpy(li->li_lksb.sb_lvbptr, kparams->lvb, DLM_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,
                    kparams->range.ra_end ? &kparams->range : NULL);

      if (status)
            goto out_err;

      /* If it succeeded (this far) with a new lock then keep track of
         it on the file's lkb list */
      if (!(kparams->flags & DLM_LKF_CONVERT)) {

            spin_lock(&fi->fi_lkb_lock);
            list_add(&li->li_ownerqueue, &fi->fi_lkb_list);
            set_bit(LI_FLAG_ONLIST, &li->li_flags);
            spin_unlock(&fi->fi_lkb_lock);

            up(&li->li_firstlock);
      }

      /* 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(li);
      }
      return status;
}

static int do_user_unlock(struct file_info *fi, uint8_t cmd, struct dlm_lock_params *kparams)
{
      struct lock_info *li;
      struct dlm_lkb *lkb;
      int status;
      int convert_cancel = 0;

      lkb = dlm_get_lkb(fi->fi_ls->ls_lockspace, kparams->lkid);
      if (!lkb) {
            return -EINVAL;
      }

      /* Cancelling a conversion doesn't remove the lock...*/
      if (kparams->flags & DLM_LKF_CANCEL &&
          lkb->lkb_status == GDLM_LKSTS_CONVERT) {
            convert_cancel = 1;
      }

      li = (struct lock_info *)lkb->lkb_astparam;
      if (!li) {
            li = allocate_lockinfo(fi, cmd, kparams);
            spin_lock(&fi->fi_lkb_lock);
            list_add(&li->li_ownerqueue, &fi->fi_lkb_list);
            set_bit(LI_FLAG_ONLIST, &li->li_flags);
            spin_unlock(&fi->fi_lkb_lock);
      }
      if (!li)
            return -ENOMEM;

      if (li->li_magic != LOCKINFO_MAGIC)
            return -EINVAL;

      /* Wait until dlm_lock() has completed */
      if (!test_bit(LI_FLAG_ONLIST, &li->li_flags)) {
            down(&li->li_firstlock);
            up(&li->li_firstlock);
      }

      li->li_user_lksb = kparams->lksb;
      li->li_castparam = kparams->castparam;
      li->li_cmd       = cmd;

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

      /* Have to do it here cos the lkb may not exist after
       * dlm_unlock() */
      if (!convert_cancel) {
            spin_lock(&fi->fi_lkb_lock);
            list_del(&li->li_ownerqueue);
            clear_bit(LI_FLAG_ONLIST, &li->li_flags);
            spin_unlock(&fi->fi_lkb_lock);
      }

      /* Use existing lksb & astparams */
      status = dlm_unlock(fi->fi_ls->ls_lockspace,
                       kparams->lkid,
                       kparams->flags, &li->li_lksb, li);
      if (status && !convert_cancel) {
            /* It failed, put it back on the list */
            spin_lock(&fi->fi_lkb_lock);
            list_add(&li->li_ownerqueue, &fi->fi_lkb_list);
            set_bit(LI_FLAG_ONLIST, &li->li_flags);
            spin_unlock(&fi->fi_lkb_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;

      if (count < sizeof(struct dlm_write_request)-1) /* -1 because lock name is optional */
            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;

      /* 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_QUERY:
            if (!fi) goto out_sig;
            status = do_user_query(fi, kparams->cmd, &kparams->i.query);
            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;
}

/* Called when the cluster is shutdown uncleanly, all lockspaces
   have been summarily removed */
void dlm_device_free_devices()
{
      struct user_ls *tmp;
      struct user_ls *lsinfo;

      down(&user_ls_lock);
      list_for_each_entry_safe(lsinfo, tmp, &user_ls_list, ls_list) {
            misc_deregister(&lsinfo->ls_miscinfo);

            /* Tidy up, but don't delete the lsinfo struct until
               all the users have closed their devices */
            list_del(&lsinfo->ls_list);
            set_bit(LS_FLAG_DELETED, &lsinfo->ls_flags);
            lsinfo->ls_lockspace = NULL;
      }
      up(&user_ls_lock);
}

static struct file_operations _dlm_fops = {
      .open    = dlm_open,
      .release = dlm_close,
      .ioctl   = dlm_ioctl,
      .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
 */
int dlm_device_init(void)
{
      int r;

      INIT_LIST_HEAD(&user_ls_list);
      init_MUTEX(&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) {
            log_print("misc_register failed for DLM control device");
            return r;
      }

      return 0;
}

void dlm_device_exit(void)
{
      misc_deregister(&ctl_device);
}

/*
 * Overrides for Emacs so that we follow Linus's tabbing style.
 * Emacs will notice this stuff at the end of the file and automatically
 * adjust the settings for this buffer only.  This must remain at the end
 * of the file.
 * ---------------------------------------------------------------------------
 * Local variables:
 * c-file-style: "linux"
 * End:
 */

Generated by  Doxygen 1.6.0   Back to index