/*
 * QUOTA    An implementation of the diskquota system for the LINUX
 *          operating system. QUOTA is implemented using the BSD systemcall
 *          interface as the means of communication with the user level. Should
 *          work for all filesystems because of integration into the VFS layer of the
 *          operating system. This is based on the Melbourne quota system wich uses
 *          both user and group quota files.
 * 
 *          Main layer of quota management
 * 
 * Version: $Id: dquot.c,v 2.5 1993/06/16 16:42:57 root Exp root $
 * 
 * Authors: Marco van Wieringen <v892273@si.hhs.nl> <mvw@hacktic.nl>
 *          Edvard Tuinder <v892231@si.hhs.nl> <etuinder@hacktic.nl>
 * 
 *          This program is free software; you can redistribute it and/or modify it
 *          under the terms of the GNU General Public License as published by the
 *          Free Software Foundation; either version 2 of the License, or (at your
 *          option) any later version.
 */

#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/quota.h>
#include <linux/string.h>
#include <linux/fcntl.h>
#include <linux/stat.h>
#include <linux/segment.h>

#include <asm/segment.h>
#include <sys/sysmacros.h>

/* Global tables */
static struct device_list *devicelist[MAXQUOTAS]; /* Linked list with enabled devices */
static struct device_list *mru_device[MAXQUOTAS]; /* Most recent used device */
static struct dquot *mru_dquot[MAXQUOTAS]; /* Most recent used id on the device in mru_device */

static inline char *typetoname(int type)
{
   switch (type) {
      case USRQUOTA:
         return "uid";
      case GRPQUOTA:
         return "gid";
   }
}

/*
 * Functions for management of devices.
 */
static struct device_list *lookup_device(dev_t dev, int type)
{
   register struct device_list *lptr;

   if (devicelist[type] == (struct device_list *) 0)
      return ((struct device_list *) 0);

   if (mru_device[type] != (struct device_list *) 0 && mru_device[type]->dq_dev == dev)
      return (mru_device[type]);

   for (lptr = devicelist[type]; lptr != (struct device_list *)0; lptr = lptr->next)
      if (lptr->dq_dev == dev)
         return (lptr);

   return ((struct device_list *) 0);
}

static struct device_list *add_device(dev_t dev, int type)
{
   register struct device_list *lptr;

   if ((lptr = (struct device_list *) kmalloc(sizeof(struct device_list), GFP_KERNEL)) ==
       (struct device_list *) 0)
      return ((struct device_list *) 0);
   memset(lptr, 0, sizeof(struct device_list));

   lptr->dq_dev = dev;
   lptr->dq_dirt = 0;
   lptr->dq_quota = (struct dquot *) 0;

   lptr->next = devicelist[type];
   devicelist[type] = lptr;

   return (lptr);
}

static void remove_device(dev_t dev, int type)
{
   register struct device_list *lptr, *tmp;
   register struct dquot *idp, *tofree;

   if (devicelist[type] == (struct device_list *) 0)
      return;

   if (mru_device[type] != (struct device_list *) 0 && mru_device[type]->dq_dev == dev) {
      mru_device[type] = (struct device_list *) 0;
      mru_dquot[type] = (struct dquot *) 0;
   }
   lptr = devicelist[type];
   if (lptr->dq_dev == dev)
      devicelist[type] = lptr->next;
   else {
      while (lptr->next != (struct device_list *) 0) {
         if (lptr->next->dq_dev == dev)
            break;
         lptr = lptr->next;
      }

      tmp = lptr->next;
      lptr->next = lptr->next->next;
      lptr = tmp;
   }

   idp = lptr->dq_quota;
   while (idp != (struct dquot *) 0) {
      tofree = idp;
      idp = idp->next;
      kfree_s(tofree, sizeof(struct dquot));
   }

   kfree_s(lptr, sizeof(struct device_list));
}

/*
 * Functions for management of dquot structs
 * 
 * Locking of dqblk for a user or group.
 */
static void wait_on_dqblk(struct dquot * dquot)
{
   struct wait_queue wait = {current, NULL};

   add_wait_queue(&dquot->dq_wait, &wait);
   while (dquot->dq_flags & DQ_LOCKED) {
      current->state = TASK_UNINTERRUPTIBLE;
      schedule();
   }
   remove_wait_queue(&dquot->dq_wait, &wait);
   current->state = TASK_RUNNING;
}

static inline void lock_dquot(struct dquot * dquot)
{
   if (dquot != (struct dquot *)0) {
      while (dquot->dq_flags & DQ_LOCKED) {
         dquot->dq_flags |= DQ_WANT;
         wait_on_dqblk(dquot);
      }
      dquot->dq_flags |= DQ_LOCKED;
   }
}

static inline void unlock_dquot(struct dquot * dquot)
{
   if (dquot != (struct dquot *)0) {
      dquot->dq_flags &= ~DQ_LOCKED;
      if (dquot->dq_flags & DQ_WANT) {
         dquot->dq_flags &= ~DQ_WANT;
         wake_up(&dquot->dq_wait);
      }
   }
}

static struct dquot *lookup_dquot(struct device_list *device, int id, int type)
{
   register struct dquot *lptr = (struct dquot *) 0;

   if (id <= ID_NO_QUOTA)
      return ((struct dquot *) 0);

   /*
    * First fast lookup when same as used before.
    */
   if (mru_device[type] != (struct device_list *) 0 && mru_dquot[type] != (struct dquot *) 0 &&
       mru_dquot[type]->dq_id == id && mru_device[type]->dq_dev == device->dq_dev) {
      lock_dquot(mru_dquot[type]);
      return (mru_dquot[type]);
   }

   for (lptr = device->dq_quota; lptr != (struct dquot *) 0; lptr = lptr->next)
      if (lptr->dq_id == id) {
         mru_device[type] = device;
         mru_dquot[type] = lptr;
         lock_dquot(lptr);
         return (lptr);
      }
   return ((struct dquot *) 0);
}

static struct dquot *add_dquot(struct device_list * device, int id)
{
   register struct dquot *lptr;

   if ((lptr = (struct dquot *) kmalloc(sizeof(struct dquot), GFP_KERNEL)) ==
       (struct dquot *) 0)
      return ((struct dquot *) 0);
   memset(lptr, 0, sizeof(struct dquot));

   lptr->dq_id = id;
   lptr->dq_btime = lptr->dq_itime = (time_t) 0;
   lptr->dq_flags |= DQ_LOCKED; /* lock it right away */

   lptr->next = device->dq_quota;
   device->dq_quota = lptr;

   return (lptr);
}

static void remove_dquot(struct device_list * device, int id, int type)
{
   register struct dquot *lptr, *tmp;

   if (mru_dquot[type] != (struct dquot *) 0 && mru_dquot[type]->dq_id == id)
      mru_dquot[type] = (struct dquot *) 0;

   lptr = device->dq_quota;
   if (lptr->dq_id == id)
      device->dq_quota = lptr->next;
   else {
      while (lptr->next != (struct dquot *) 0) {
         if (lptr->next->dq_id == id)
            break;
         lptr = lptr->next;
      }

      tmp = lptr->next;
      lptr->next = lptr->next->next;
      lptr = tmp;
   }

   kfree_s(lptr, sizeof(struct dquot));
}

static int check_idq(struct device_list * device, struct dquot * dquot, int id, int type)
{
   if (dquot->dq_isoftlimit == 0 && dquot->dq_ihardlimit == 0)
      return QUOTA_OK;

   if (dquot->dq_ihardlimit && (dquot->dq_curinodes + 1) > dquot->dq_ihardlimit) {
      if ((dquot->dq_flags & DQ_INODES) == 0) {
         printk("File LIMIT exceeded on device (%d,%d) for %s %d. !! NO MORE !!\n",
                major(device->dq_dev), minor(device->dq_dev), typetoname(type), id);
         dquot->dq_flags &= DQ_INODES;
      }
      return NO_QUOTA;
   }
   if (dquot->dq_isoftlimit && (dquot->dq_curinodes + 1) >= dquot->dq_isoftlimit &&
       dquot->dq_itime && CURRENT_TIME >= (dquot->dq_itime + device->dq_iexp)) {
      printk("File QUOTA exceeded TOO long on device (%d,%d) for %s %d. !! NO MORE !!\n",
             major(device->dq_dev), minor(device->dq_dev), typetoname(type), id);
      return NO_QUOTA;
   }
   if (dquot->dq_isoftlimit && (dquot->dq_curinodes + 1) >= dquot->dq_isoftlimit &&
       dquot->dq_itime == 0) {
      printk("File QUOTA exceeded on device (%d,%d) for %s %d\n",
             major(device->dq_dev), minor(device->dq_dev), typetoname(type), id);
      dquot->dq_itime = CURRENT_TIME;
   }
   return QUOTA_OK;
   /* NOTREACHED */
}

static int check_bdq(struct device_list * device, struct dquot * dquot, int id,
             int type, u_long blocks)
{
   if (dquot->dq_bsoftlimit == 0 && dquot->dq_bhardlimit == 0)
      return QUOTA_OK;

   if (dquot->dq_bhardlimit && (dquot->dq_curblocks + blocks) > dquot->dq_bhardlimit) {
      if ((dquot->dq_flags & DQ_BLKS) == 0) {
         printk("Block LIMIT exceeded on device (%d,%d) for %s %d. !! NO MORE !!\n",
                major(device->dq_dev), minor(device->dq_dev), typetoname(type), id);
         dquot->dq_flags &= DQ_BLKS;
      }
      return NO_QUOTA;
   }
   if (dquot->dq_bsoftlimit && (dquot->dq_curblocks + blocks) >= dquot->dq_bsoftlimit &&
       dquot->dq_btime && CURRENT_TIME >= (dquot->dq_btime + device->dq_bexp)) {
      printk("Block QUOTA exceeded TOO long on device (%d,%d) for %s %d. !! NO MORE !!\n",
             major(device->dq_dev), minor(device->dq_dev), typetoname(type), id);
      return NO_QUOTA;
   }
   if (dquot->dq_bsoftlimit && (dquot->dq_curblocks + blocks) >= dquot->dq_bsoftlimit &&
       dquot->dq_btime == 0) {
      printk("Block QUOTA exceeded on (%d,%d) for %s %d\n",
             major(device->dq_dev), minor(device->dq_dev), typetoname(type), id);
      dquot->dq_btime = CURRENT_TIME;
   }
   return QUOTA_OK;
   /* NOTREACHED */
}

static void remove_idq(struct dquot * dquot)
{
   if (dquot->dq_curinodes > 0)
      dquot->dq_curinodes--;

   if (dquot->dq_curinodes < dquot->dq_isoftlimit) {
      dquot->dq_itime = (time_t) 0;
      dquot->dq_flags &= ~DQ_INODES;
   }
   dquot->dq_flags |= DQ_MOD;
}

static void remove_bdq(struct dquot * dquot, u_long blocks)
{
   if (dquot->dq_curblocks >= blocks)
      dquot->dq_curblocks -= blocks;
   else
      dquot->dq_curblocks = 0;

   if (dquot->dq_curblocks < dquot->dq_bhardlimit) {
      dquot->dq_btime = (time_t) 0;
      dquot->dq_flags &= ~DQ_BLKS;
   }
   dquot->dq_flags |= DQ_MOD;
}

/*
 * Realy sync the quota info to the quota file.
 */
static int sync_device(struct device_list * device, int type)
{
   register struct dquot *dquot = (struct dquot *) 0;
   struct dqblk exp_times;
   unsigned short fs;

   if (device->dq_dirt == 0)
      return 0;

   if (device->dq_file.f_op->open)
      if (device->dq_file.f_op->open(device->dq_file.f_inode, &device->dq_file))
         return -EIO;

   if (!device->dq_file.f_op->write)
      return -EIO;

   set_fs(fs);
   set_fs(KERNEL_DS);

   /* First write expire times */
   memset(&exp_times, 0, sizeof(struct dqblk));
   exp_times.dqb_itime = device->dq_iexp;
   exp_times.dqb_btime = device->dq_bexp;

   if (device->dq_file.f_op->lseek) {
      if (device->dq_file.f_op->lseek(device->dq_file.f_inode, &device->dq_file, 0, 0) != 0)
         goto end_sync;
   } else
      device->dq_file.f_pos = 0;

   if (device->dq_file.f_op->write(device->dq_file.f_inode, &device->dq_file,
       (char *)&exp_times, sizeof(struct dqblk)) != sizeof(struct dqblk))
      goto end_sync;

   dquot = device->dq_quota;
   while (dquot != (struct dquot *)0) {
      if (dquot->dq_flags & DQ_MOD) {
         if (device->dq_file.f_op->lseek) {
            if (device->dq_file.f_op->lseek(device->dq_file.f_inode, &device->dq_file,
                dqoff(dquot->dq_id), 0) != dqoff(dquot->dq_id))
               goto end_sync;
         } else
            device->dq_file.f_pos = dqoff(dquot->dq_id);

         if (device->dq_file.f_op->write(device->dq_file.f_inode, &device->dq_file,
             (char *) &dquot->dq_dqb, sizeof(struct dqblk)) != sizeof(struct dqblk))
            goto end_sync;
         dquot->dq_flags &= ~DQ_MOD;
      }
      dquot = dquot->next;
   }

   set_fs(fs);

   device->dq_dirt = 0;
   return 0;

end_sync:
   set_fs(fs);
   return -EIO;
}

static int set_dqblk(struct device_list * device, int id, int type, int flags, struct dqblk *dqblk)
{
   register struct dquot *dquot = (struct dquot *) 0;
   struct dqblk dq_dqblk;
   int error;

   /* No quota enabled for users and groups below ID_NO_QUOTA */
   if (id > 0 && id <= ID_NO_QUOTA)
      return 0;

   if (dqblk == (struct dqblk *)0)
      return -EFAULT;

   if (flags & QUOTA_SYSCALL) {
      if ((error = verify_area(VERIFY_READ, dqblk, sizeof(struct dqblk))) != 0)
         return error;
      memcpy_fromfs(&dq_dqblk, dqblk, sizeof(struct dqblk));
   } else {
      memcpy(&dq_dqblk, dqblk, sizeof(struct dqblk));
   }

   /* set expiration times */
   if (id == 0) {
      device->dq_iexp = (dq_dqblk.dqb_itime) ? dq_dqblk.dqb_itime : MAX_IQ_TIME;
      device->dq_bexp = (dq_dqblk.dqb_btime) ? dq_dqblk.dqb_btime : MAX_DQ_TIME;
      device->dq_dirt = 1;
      return 0;
   }

   dquot = lookup_dquot(device, id, type);
   if (dq_dqblk.dqb_bhardlimit == 0 && dq_dqblk.dqb_bsoftlimit == 0 &&
       dq_dqblk.dqb_ihardlimit == 0 && dq_dqblk.dqb_isoftlimit == 0) {
      if (dquot == (struct dquot *) 0)
         return 0; /* No quota set */

      dquot->dq_flags |= DQ_REMOVE; /* sync and remove at the end */
   }

   if (dquot == (struct dquot *) 0)
      if ((dquot = add_dquot(device, id)) == (struct dquot *) 0)
         return -EUSERS;

   if ((flags & SET_QUOTA) || (flags & SET_QLIMIT)) {
      dquot->dq_bhardlimit = dq_dqblk.dqb_bhardlimit;
      dquot->dq_bsoftlimit = dq_dqblk.dqb_bsoftlimit;
      dquot->dq_ihardlimit = dq_dqblk.dqb_ihardlimit;
      dquot->dq_isoftlimit = dq_dqblk.dqb_isoftlimit;
      if ((flags & QUOTA_SYSCALL) == 0) {
         dquot->dq_btime = dq_dqblk.dqb_btime;
         dquot->dq_itime = dq_dqblk.dqb_itime;
      }
   }

   if ((flags & SET_QUOTA) || (flags & SET_USE)) {
      dquot->dq_curblocks = dq_dqblk.dqb_curblocks;
      dquot->dq_curinodes = dq_dqblk.dqb_curinodes;
      if (dquot->dq_btime && dquot->dq_curblocks < dquot->dq_bsoftlimit)
         dquot->dq_btime = (time_t) 0;
      if (dquot->dq_itime && dquot->dq_curinodes < dquot->dq_isoftlimit)
         dquot->dq_itime = (time_t) 0;
   }

   device->dq_dirt = 1;
   dquot->dq_flags |= DQ_MOD;

   if (dquot->dq_flags & DQ_REMOVE) {
      sync_device(device, type);
      remove_dquot(device, id, type);
   } else
      unlock_dquot(dquot);

   return 0;
}

/*
 * ====================== Entry points of module ======================
 */

/*
 * This simple algorithm calculates the size of a file in blocks.
 * It is not perfect but works most of the time.
 */
u_long isize_to_blocks(size_t isize, size_t blksize)
{
   u_long blocks;
   u_long indirect;

   if (!blksize)
     blksize = BLOCK_SIZE;

   blocks = (isize / blksize) + ((isize % blksize) ? 1 : 0);
   if (blocks > 10) {
      indirect = (blocks - 11) / 256 + 1; /* single indirect blocks */
      if (blocks > (10 + 256)) {
         indirect += (blocks - 267) / (256 * 256) + 1; /* double indirect blocks */
         if (blocks > (10 + 256 + (256 * 256)))
            indirect++; /* triple indirect blocks */
      }
      blocks += indirect;
   }
   return blocks;
}

/*
 * Allocate the number of inodes and blocks from a diskquota.
 */
int quota_alloc(dev_t dev, uid_t uid, gid_t gid, u_long inodes, u_long blocks)
{
   struct device_list *usr_device = (struct device_list *) 0,
                      *grp_device = (struct device_list *) 0;
   struct dquot *usr_dquot = (struct dquot *) 0, *grp_dquot = (struct dquot *) 0;

   if ((usr_device = lookup_device(dev, USRQUOTA)) != (struct device_list *) 0) {
      if ((usr_dquot = lookup_dquot(usr_device, uid, USRQUOTA)) != (struct dquot *) 0) {
         if (check_idq(usr_device, usr_dquot, uid, USRQUOTA) == NO_QUOTA ||
             check_bdq(usr_device, usr_dquot, uid, USRQUOTA, blocks) == NO_QUOTA) {
            unlock_dquot(usr_dquot);
            return NO_QUOTA;
         }
      }
   }

   if ((grp_device = lookup_device(dev, GRPQUOTA)) != (struct device_list *)0) {
      if ((grp_dquot = lookup_dquot(grp_device, gid, GRPQUOTA)) != (struct dquot *) 0) {
         if (check_idq(grp_device, grp_dquot, gid, GRPQUOTA) == NO_QUOTA ||
             check_bdq(grp_device, grp_dquot, gid, GRPQUOTA, blocks) == NO_QUOTA) {
            unlock_dquot(usr_dquot);
            unlock_dquot(grp_dquot);
            return NO_QUOTA;
         }
      }
   }

   /* Have exclusive lock on both quotas if needed and can allocate */
   if (usr_dquot != (struct dquot *) 0) {
      if (inodes)
         usr_dquot->dq_curinodes++;
      if (blocks)
         usr_dquot->dq_curblocks += blocks;
      usr_dquot->dq_flags |= DQ_MOD;
      unlock_dquot(usr_dquot);
      usr_device->dq_dirt = 1;
   }

   if (grp_dquot != (struct dquot *) 0) {
      if (inodes)
         grp_dquot->dq_curinodes++;
      if (blocks)
         grp_dquot->dq_curblocks += blocks;
      grp_dquot->dq_flags |= DQ_MOD;
      unlock_dquot(grp_dquot);
      grp_device->dq_dirt = 1;
   }
   return QUOTA_OK;
   /* NOTREACHED */
}

/*
 * Transfer the number of inode and blocks from one diskquota to an other.
 */
int quota_transfer(dev_t dev, uid_t olduid, uid_t newuid, gid_t oldgid, gid_t newgid,
          u_long inodes, u_long blocks)
{
   struct device_list *usr_device, *grp_device;
   struct dquot   *old_usr_dquot = (struct dquot *) 0, *new_usr_dquot = (struct dquot *) 0,
                  *old_grp_dquot = (struct dquot *) 0, *new_grp_dquot = (struct dquot *) 0;

   if (olduid != newuid && (usr_device = lookup_device(dev, USRQUOTA)) != (struct device_list *)0) {
      if ((new_usr_dquot = lookup_dquot(usr_device, newuid, USRQUOTA)) != (struct dquot *) 0) {
         if (check_idq(usr_device, new_usr_dquot, newuid, USRQUOTA) == NO_QUOTA ||
             check_bdq(usr_device, new_usr_dquot, newuid, USRQUOTA, blocks) == NO_QUOTA) {
            unlock_dquot(new_usr_dquot);
            return NO_QUOTA;
         }
      }
   }

   if (oldgid != newgid && (grp_device = lookup_device(dev, GRPQUOTA)) != (struct device_list *)0) {
      if ((new_grp_dquot = lookup_dquot(grp_device, newgid, GRPQUOTA)) != (struct dquot *) 0) {
         if (check_idq(grp_device, new_grp_dquot, newgid, GRPQUOTA) == NO_QUOTA ||
             check_bdq(grp_device, new_grp_dquot, newgid, GRPQUOTA, blocks) == NO_QUOTA) {
            unlock_dquot(new_usr_dquot);
            unlock_dquot(new_grp_dquot);
            return NO_QUOTA;
         }
      }
   }

   /* Have exclusive lock on both quotas if needed and can allocate */
   if (olduid != newuid && usr_device != (struct device_list *)0) {
      if ((old_usr_dquot = lookup_dquot(usr_device, olduid, USRQUOTA)) != (struct dquot *) 0) {
         /* Remove it from old */
         if (inodes)
            remove_idq(old_usr_dquot);
         if (blocks)
            remove_bdq(old_usr_dquot, blocks);
         unlock_dquot(old_usr_dquot);
      }

      /* Move it to new diskquota */
      if (new_usr_dquot != (struct dquot *)0) {
         if (inodes)
            new_usr_dquot->dq_curinodes++;
         if (blocks)
            new_usr_dquot->dq_curblocks += blocks;
         new_usr_dquot->dq_flags |= DQ_MOD;
         unlock_dquot(new_usr_dquot);
      }
      usr_device->dq_dirt = 1;
   }
   

   if (oldgid != newgid && grp_device != (struct device_list *)0) {
      if ((old_grp_dquot = lookup_dquot(grp_device, oldgid, GRPQUOTA)) != (struct dquot *) 0) {
         /* Remove it from old */
         if (inodes)
            remove_idq(old_grp_dquot);
         if (blocks)
            remove_bdq(old_grp_dquot, blocks);
         unlock_dquot(old_grp_dquot);
      }

      /* Move it to new diskquota */
      if (new_grp_dquot != (struct dquot *) 0) {
         if (inodes)
            new_grp_dquot->dq_curinodes++;
         if (blocks)
            new_grp_dquot->dq_curblocks += blocks;
         new_grp_dquot->dq_flags |= DQ_MOD;
         unlock_dquot(new_grp_dquot);
      }
      grp_device->dq_dirt = 1;
   }
   return QUOTA_OK;
   /* NOTREACHED */
}

/*
 * Remove the number of inodes and blocks from a diskquota.
 */
void quota_remove(dev_t dev, uid_t uid, gid_t gid, u_long inodes, u_long blocks)
{
   struct device_list *usr_device, *grp_device;
   struct dquot   *usr_dquot, *grp_dquot;

   if ((usr_device = lookup_device(dev, USRQUOTA)) != (struct device_list *) 0) {
      if ((usr_dquot = lookup_dquot(usr_device, uid, USRQUOTA)) != (struct dquot *) 0) {
         if (inodes)
            remove_idq(usr_dquot);
         if (blocks)
            remove_bdq(usr_dquot, blocks);
         unlock_dquot(usr_dquot);
         usr_device->dq_dirt = 1;
      }
   }
   if ((grp_device = lookup_device(dev, GRPQUOTA)) != (struct device_list *) 0) {
      if ((grp_dquot = lookup_dquot(grp_device, gid, GRPQUOTA)) != (struct dquot *) 0) {
         if (inodes)
            remove_idq(grp_dquot);
         if (blocks)
            remove_bdq(grp_dquot, blocks);
         unlock_dquot(grp_dquot);
         grp_device->dq_dirt = 1;
      }
   }
}

/*
 * Following functions used by systemcall interface don't use it your self !!
 */
int set_quota(dev_t dev, int id, int type, struct dqblk * dqblk)
{
   register struct device_list *device;

   if ((device = lookup_device(dev, type)) != (struct device_list *) 0)
      return set_dqblk(device, id, type, SET_QUOTA | QUOTA_SYSCALL, dqblk);
   else
      return -ESRCH;
}

int set_use(dev_t dev, int id, int type, struct dqblk * dqblk)
{
   register struct device_list *device;

   if ((device = lookup_device(dev, type)) != (struct device_list *) 0)
      return set_dqblk(device, id, type, SET_USE | QUOTA_SYSCALL, dqblk);
   else
      return -ESRCH;
}

int set_qlimit(dev_t dev, int id, int type, struct dqblk * dqblk)
{
   register struct device_list *device;

   if ((device = lookup_device(dev, type)) != (struct device_list *) 0)
      return set_dqblk(device, id, type, SET_QLIMIT | QUOTA_SYSCALL, dqblk);
   else
      return -ESRCH;
}

int get_quota(dev_t dev, int id, int type, struct dqblk * dqblk)
{
   register struct device_list *device;
   register struct dquot *dquot;
   struct dqblk exp_times;
   int error;

   if ((device = lookup_device(dev, type)) != (struct device_list *) 0) {
      if (dqblk == (struct dqblk *) 0)
         return -EFAULT;

      if ((error = verify_area(VERIFY_WRITE, dqblk, sizeof(struct dqblk))) != 0)
         return (error);

      if (id > ID_NO_QUOTA && (dquot = lookup_dquot(device, id, type)) != (struct dquot *) 0) {
         memcpy_tofs(dqblk, (char *) &dquot->dq_dqb, sizeof(struct dqblk));
         unlock_dquot(dquot);
         return 0;
      } else {
         if (id == 0) {
            /*
             * Special case for getting of expiration
             * times
             */
            memset(&exp_times, 0, sizeof(struct dqblk));
            exp_times.dqb_btime = device->dq_iexp;
            exp_times.dqb_itime = device->dq_bexp;
            memcpy_tofs(dqblk, &exp_times, sizeof(struct dqblk));
            return 0;
         }
         return -ESRCH;
      }
   } else
      return -ESRCH;
}

/*
 * Sync quota on a device. Dev == 0 ==> sync all quotafiles.
 * Type == -1 ==> sync all types.
 */
int sync_quota(dev_t dev, int type)
{
   register struct device_list *device;
   int cnt, retval = 0;

   if (dev) {
      if (type != -1) {
         if ((device = lookup_device(dev, type)) == (struct device_list *) 0)
            return -ESRCH;
         return sync_device(device, type);
      } else {
         for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
            if ((device = lookup_device(dev, cnt)) == (struct device_list *) 0)
               continue;
            retval |= sync_device(device, cnt);
         }
         return retval;
      }
   } else {
      for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
         device = devicelist[cnt];
         while (device != (struct device_list *) 0) {
            retval |= sync_device(device, cnt);
            device = device->next;
         }
      }
      return retval;
   }
   /* NOTREACHED */
}

/*
 * Turn quota off on a device. type == -1 ==> quotaoff for all types (umount)
 */
int quota_off(dev_t dev, int type)
{
   register struct device_list *device;
   register struct inode *inode = (struct inode *) 0;
   int cnt;

   if (type != -1) {
      if ((device = lookup_device(dev, type)) == (struct device_list *) 0)
         return -ESRCH;

      (void) sync_device(device, type);   /* Yes, just like Q_SYNC... */

      if (device->dq_file.f_op->release)
         device->dq_file.f_op->release(device->dq_file.f_inode, &device->dq_file);

      if ((inode = device->dq_file.f_inode) != (struct inode *) 0)
         iput(device->dq_file.f_inode);   /* Now release the inode */

      remove_device(dev, type);
   } else {
      for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
         if ((device = lookup_device(dev, cnt)) == (struct device_list *) 0)
            continue;

         (void) sync_device(device, cnt);   /* Yes, just like Q_SYNC... */

         if (device->dq_file.f_op->release)
            device->dq_file.f_op->release(device->dq_file.f_inode, &device->dq_file);

         if ((inode = device->dq_file.f_inode) != (struct inode *) 0)
            iput(device->dq_file.f_inode);   /* Now release the inode */

         remove_device(dev, cnt);
      }
   }
   return 0;
}

int quota_on(dev_t dev, int type, char *path)
{
   register struct device_list *device;
   struct dqblk dq_dqb;
   struct inode *inode;
   char *tmp;
   int error, maxid, id = ID_NO_QUOTA;
   unsigned short  fs;

   /* Quota already enabled */
   if ((device = lookup_device(dev, type)) != (struct device_list *) 0)
      return -EBUSY;

   if ((error = getname(path, &tmp)) != 0)
      return (error);

   error = open_namei(tmp, O_RDWR, 0600, &inode, 0);
   putname(tmp);

   if (error)
      return (error);

   if (!S_ISREG(inode->i_mode) || inode->i_dev != dev) {
      iput(inode);
      return -EACCES;
   }

   if ((device = add_device(dev, type)) == (struct device_list *) 0) {
      iput(inode);
      return -EUSERS;
   }

   __asm__("mov %%fs,%0":"=r" (fs));
   if (!inode->i_op || !inode->i_op->default_file_ops)
      goto end_quotaon;

   device->dq_file.f_mode = 3;
   device->dq_file.f_flags = 0;
   device->dq_file.f_count = 1;
   device->dq_file.f_inode = inode;
   device->dq_file.f_pos = 0;
   device->dq_file.f_reada = 0;
   device->dq_file.f_op = inode->i_op->default_file_ops;

   if (device->dq_file.f_op->open)
      if (device->dq_file.f_op->open(device->dq_file.f_inode, &device->dq_file))
         goto end_quotaon;

   if (!device->dq_file.f_op->read)
      goto end_quotaon;

   maxid = inode->i_size / sizeof(struct dqblk);

   set_fs(KERNEL_DS);

   /* First read expire times */
   if (device->dq_file.f_op->read(inode, &device->dq_file, (char *)&dq_dqb,
       sizeof(struct dqblk)) != sizeof(struct dqblk))
      goto end_quotaon;
   set_dqblk(device, 0, type, SET_QUOTA, &dq_dqb);

   /* Seek to first real entry */
   if (device->dq_file.f_op->lseek) {
      if (device->dq_file.f_op->lseek(inode, &device->dq_file, dqoff(id), 0) != dqoff(id))
         goto end_quotaon;
   } else
      device->dq_file.f_pos = dqoff(id);

   while (id < maxid) {
      if (device->dq_file.f_op->read(inode, &device->dq_file, (char *)&dq_dqb, sizeof(struct dqblk)) != sizeof(struct dqblk))
         goto end_quotaon;
      set_dqblk(device, id++, type, SET_QUOTA, &dq_dqb);
   }
   set_fs(fs);

   return 0;

end_quotaon:
   set_fs(fs);
   iput(inode);
   remove_device(device->dq_dev, type);
   return -EIO;
}

void quota_init(void)
{
   unsigned short  cnt;

   for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
      devicelist[cnt] = (struct device_list *) 0;
      mru_dquot[cnt] = (struct dquot *) 0;
      mru_device[cnt] = (struct device_list *) 0;
   }
}
