/* * Implementation of the diskquota system for the LINUX operating * system. QUOTA is implemented using the BSD system call interface as * the means of communication with the user level. Currently only the * ext2 filesystem has support for disk quotas. Other filesystems may * be added in the future. This file contains the generic routines * called by the different filesystems on allocation of an inode or * block. These routines take care of the administration needed to * have a consistent diskquota tracking system. The ideas of both * user and group quotas are based on the Melbourne quota system as * used on BSD derived systems. The internal implementation is * based on one of the several variants of the LINUX inode-subsystem * with added complexity of the diskquota system. * * Version: $Id: dquot.c,v 6.3 1996/11/17 18:35:34 mvw Exp mvw $ * * Author: Marco van Wieringen * * Fixes: Dmitry Gorodchanin , 11 Feb 96 * * Revised list management to avoid races * -- Bill Hawes, , 9/98 * * Fixed races in dquot_transfer(), dqget() and dquot_alloc_...(). * As the consequence the locking was moved from dquot_decr_...(), * dquot_incr_...() to calling functions. * invalidate_dquots() now writes modified dquots. * Serialized quota_off() and quota_on() for mount point. * Fixed a few bugs in grow_dquots(). * Fixed deadlock in write_dquot() - we no longer account quotas on * quota files * remove_dquot_ref() moved to inode.c - it now traverses through inodes * add_dquot_ref() restarts after blocking * Added check for bogus uid and fixed check for group in quotactl. * Jan Kara, , sponsored by SuSE CR, 10-11/99 * * Used struct list_head instead of own list struct * Invalidation of dquots with dq_count > 0 no longer possible * Improved free_dquots list management * Quota and i_blocks are now updated in one place to avoid races * Warnings are now delayed so we won't block in critical section * Write updated not to require dquot lock * Jan Kara, , 9/2000 * * Added dynamic quota structure allocation * Jan Kara 12/2000 * * (C) Copyright 1994 - 1997 Marco van Wieringen */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define __DQUOT_VERSION__ "dquot_6.4.0" int nr_dquots, nr_free_dquots; static char *quotatypes[] = INITQFNAMES; static inline struct quota_mount_options *sb_dqopt(struct super_block *sb) { return &sb->s_dquot; } /* * Dquot List Management: * The quota code uses three lists for dquot management: the inuse_list, * free_dquots, and dquot_hash[] array. A single dquot structure may be * on all three lists, depending on its current state. * * All dquots are placed to the end of inuse_list when first created, and this * list is used for the sync and invalidate operations, which must look * at every dquot. * * Unused dquots (dq_count == 0) are added to the free_dquots list when * freed, and this list is searched whenever we need an available dquot. * Dquots are removed from the list as soon as they are used again, and * nr_free_dquots gives the number of dquots on the list. When dquot is * invalidated it's completely released from memory. * * Dquots with a specific identity (device, type and id) are placed on * one of the dquot_hash[] hash chains. The provides an efficient search * mechanism to locate a specific dquot. */ /* * Note that any operation which operates on dquot data (ie. dq_dqb) mustn't * block while it's updating/reading it. Otherwise races would occur. * * Locked dquots might not be referenced in inodes - operations like * add_dquot_space() does dqduplicate() and would complain. Currently * dquot it locked only once in its existence - when it's being read * to memory on first dqget() and at that time it can't be referenced * from inode. Write operations on dquots don't hold dquot lock as they * copy data to internal buffers before writing anyway and copying as well * as any data update should be atomic. Also nobody can change used * entries in dquot structure as this is done only when quota is destroyed * and invalidate_dquots() waits for dquot to have dq_count == 0. */ static LIST_HEAD(inuse_list); static LIST_HEAD(free_dquots); static struct list_head dquot_hash[NR_DQHASH]; static struct dqstats dqstats; static void dqput(struct dquot *); static struct dquot *dqduplicate(struct dquot *); static inline char is_enabled(struct quota_mount_options *dqopt, short type) { switch (type) { case USRQUOTA: return((dqopt->flags & DQUOT_USR_ENABLED) != 0); case GRPQUOTA: return((dqopt->flags & DQUOT_GRP_ENABLED) != 0); } return(0); } static inline char sb_has_quota_enabled(struct super_block *sb, short type) { return is_enabled(sb_dqopt(sb), type); } static inline int const hashfn(kdev_t dev, unsigned int id, short type) { return((HASHDEV(dev) ^ id) * (MAXQUOTAS - type)) % NR_DQHASH; } static inline void insert_dquot_hash(struct dquot *dquot) { struct list_head *head = dquot_hash + hashfn(dquot->dq_dev, dquot->dq_id, dquot->dq_type); list_add(&dquot->dq_hash, head); } static inline void remove_dquot_hash(struct dquot *dquot) { list_del(&dquot->dq_hash); INIT_LIST_HEAD(&dquot->dq_hash); } static inline struct dquot *find_dquot(unsigned int hashent, kdev_t dev, unsigned int id, short type) { struct list_head *head; struct dquot *dquot; for (head = dquot_hash[hashent].next; head != dquot_hash+hashent; head = head->next) { dquot = list_entry(head, struct dquot, dq_hash); if (dquot->dq_dev == dev && dquot->dq_id == id && dquot->dq_type == type) return dquot; } return NODQUOT; } /* Add a dquot to the head of the free list */ static inline void put_dquot_head(struct dquot *dquot) { list_add(&dquot->dq_free, &free_dquots); nr_free_dquots++; } /* Add a dquot to the tail of the free list */ static inline void put_dquot_last(struct dquot *dquot) { list_add(&dquot->dq_free, free_dquots.prev); nr_free_dquots++; } /* Move dquot to the head of free list (it must be already on it) */ static inline void move_dquot_head(struct dquot *dquot) { list_del(&dquot->dq_free); list_add(&dquot->dq_free, &free_dquots); } static inline void remove_free_dquot(struct dquot *dquot) { if (list_empty(&dquot->dq_free)) return; list_del(&dquot->dq_free); INIT_LIST_HEAD(&dquot->dq_free); nr_free_dquots--; } static inline void put_inuse(struct dquot *dquot) { /* We add to the back of inuse list so we don't have to restart * when traversing this list and we block */ list_add(&dquot->dq_inuse, inuse_list.prev); nr_dquots++; } static inline void remove_inuse(struct dquot *dquot) { nr_dquots--; list_del(&dquot->dq_inuse); } static void __wait_on_dquot(struct dquot *dquot) { DECLARE_WAITQUEUE(wait, current); add_wait_queue(&dquot->dq_wait_lock, &wait); repeat: set_current_state(TASK_UNINTERRUPTIBLE); if (dquot->dq_flags & DQ_LOCKED) { schedule(); goto repeat; } remove_wait_queue(&dquot->dq_wait_lock, &wait); current->state = TASK_RUNNING; } static inline void wait_on_dquot(struct dquot *dquot) { if (dquot->dq_flags & DQ_LOCKED) __wait_on_dquot(dquot); } static inline void lock_dquot(struct dquot *dquot) { wait_on_dquot(dquot); dquot->dq_flags |= DQ_LOCKED; } static inline void unlock_dquot(struct dquot *dquot) { dquot->dq_flags &= ~DQ_LOCKED; wake_up(&dquot->dq_wait_lock); } static void __wait_dquot_unused(struct dquot *dquot) { DECLARE_WAITQUEUE(wait, current); add_wait_queue(&dquot->dq_wait_free, &wait); repeat: set_current_state(TASK_UNINTERRUPTIBLE); if (dquot->dq_count) { schedule(); goto repeat; } remove_wait_queue(&dquot->dq_wait_free, &wait); current->state = TASK_RUNNING; } /* * We don't have to be afraid of deadlocks as we never have quotas on quota files... */ static void write_dquot(struct dquot *dquot) { short type = dquot->dq_type; struct file *filp; mm_segment_t fs; loff_t offset; ssize_t ret; struct semaphore *sem = &dquot->dq_sb->s_dquot.dqio_sem; struct dqblk dqbuf; down(sem); filp = dquot->dq_sb->s_dquot.files[type]; offset = dqoff(dquot->dq_id); fs = get_fs(); set_fs(KERNEL_DS); /* * Note: clear the DQ_MOD flag unconditionally, * so we don't loop forever on failure. */ memcpy(&dqbuf, &dquot->dq_dqb, sizeof(struct dqblk)); dquot->dq_flags &= ~DQ_MOD; ret = 0; if (filp) ret = filp->f_op->write(filp, (char *)&dqbuf, sizeof(struct dqblk), &offset); if (ret != sizeof(struct dqblk)) printk(KERN_WARNING "VFS: dquota write failed on dev %s\n", kdevname(dquot->dq_dev)); set_fs(fs); up(sem); dqstats.writes++; } static void read_dquot(struct dquot *dquot) { short type = dquot->dq_type; struct file *filp; mm_segment_t fs; loff_t offset; filp = dquot->dq_sb->s_dquot.files[type]; if (filp == (struct file *)NULL) return; lock_dquot(dquot); if (!dquot->dq_sb) /* Invalidated quota? */ goto out_lock; /* Now we are sure filp is valid - the dquot isn't invalidated */ down(&dquot->dq_sb->s_dquot.dqio_sem); offset = dqoff(dquot->dq_id); fs = get_fs(); set_fs(KERNEL_DS); filp->f_op->read(filp, (char *)&dquot->dq_dqb, sizeof(struct dqblk), &offset); up(&dquot->dq_sb->s_dquot.dqio_sem); set_fs(fs); if (dquot->dq_bhardlimit == 0 && dquot->dq_bsoftlimit == 0 && dquot->dq_ihardlimit == 0 && dquot->dq_isoftlimit == 0) dquot->dq_flags |= DQ_FAKE; dqstats.reads++; out_lock: unlock_dquot(dquot); } /* Invalidate all dquots on the list, wait for all users. Note that this function is called * after quota is disabled so no new quota might be created. As we only insert to the end of * inuse list, we don't have to restart searching... */ static void invalidate_dquots(struct super_block *sb, short type) { struct dquot *dquot; struct list_head *head; restart: for (head = inuse_list.next; head != &inuse_list; head = head->next) { dquot = list_entry(head, struct dquot, dq_inuse); if (dquot->dq_sb != sb) continue; if (dquot->dq_type != type) continue; dquot->dq_flags |= DQ_INVAL; if (dquot->dq_count) /* * Wait for any users of quota. As we have already cleared the flags in * superblock and cleared all pointers from inodes we are assured * that there will be no new users of this quota. */ __wait_dquot_unused(dquot); /* Quota now have no users and it has been written on last dqput() */ remove_dquot_hash(dquot); remove_free_dquot(dquot); remove_inuse(dquot); kmem_cache_free(dquot_cachep, dquot); goto restart; } } int sync_dquots(kdev_t dev, short type) { struct list_head *head; struct dquot *dquot; lock_kernel(); restart: for (head = inuse_list.next; head != &inuse_list; head = head->next) { dquot = list_entry(head, struct dquot, dq_inuse); if (dev && dquot->dq_dev != dev) continue; if (type != -1 && dquot->dq_type != type) continue; if (!dquot->dq_sb) /* Invalidated? */ continue; if (!(dquot->dq_flags & (DQ_MOD | DQ_LOCKED))) continue; /* Raise use count so quota won't be invalidated. We can't use dqduplicate() as it does too many tests */ dquot->dq_count++; if (dquot->dq_flags & DQ_LOCKED) wait_on_dquot(dquot); if (dquot->dq_flags & DQ_MOD) write_dquot(dquot); dqput(dquot); goto restart; } dqstats.syncs++; unlock_kernel(); return 0; } /* Free unused dquots from cache */ static void prune_dqcache(int count) { struct list_head *head; struct dquot *dquot; head = free_dquots.prev; while (head != &free_dquots && count) { dquot = list_entry(head, struct dquot, dq_free); remove_dquot_hash(dquot); remove_free_dquot(dquot); remove_inuse(dquot); kmem_cache_free(dquot_cachep, dquot); count--; head = free_dquots.prev; } } int shrink_dqcache_memory(int priority, unsigned int gfp_mask) { lock_kernel(); prune_dqcache(nr_free_dquots / (priority + 1)); unlock_kernel(); kmem_cache_shrink(dquot_cachep); return 0; } /* NOTE: If you change this function please check whether dqput_blocks() works right... */ static void dqput(struct dquot *dquot) { if (!dquot) return; if (!dquot->dq_count) { printk("VFS: dqput: trying to free free dquot\n"); printk("VFS: device %s, dquot of %s %d\n", kdevname(dquot->dq_dev), quotatypes[dquot->dq_type], dquot->dq_id); return; } dqstats.drops++; we_slept: if (dquot->dq_count > 1) { /* We have more than one user... We can simply decrement use count */ dquot->dq_count--; return; } if (dquot->dq_flags & DQ_MOD) { write_dquot(dquot); goto we_slept; } /* sanity check */ if (!list_empty(&dquot->dq_free)) { printk(KERN_ERR "dqput: dquot already on free list??\n"); dquot->dq_count--; /* J.K. Just decrementing use count seems safer... */ return; } dquot->dq_count--; /* If dquot is going to be invalidated invalidate_dquots() is going to free it so */ if (!(dquot->dq_flags & DQ_INVAL)) put_dquot_last(dquot); /* Place at end of LRU free queue */ wake_up(&dquot->dq_wait_free); } static struct dquot *get_empty_dquot(void) { struct dquot *dquot; dquot = kmem_cache_alloc(dquot_cachep, SLAB_KERNEL); if(!dquot) return NODQUOT; memset((caddr_t)dquot, 0, sizeof(struct dquot)); init_waitqueue_head(&dquot->dq_wait_free); init_waitqueue_head(&dquot->dq_wait_lock); INIT_LIST_HEAD(&dquot->dq_free); INIT_LIST_HEAD(&dquot->dq_inuse); INIT_LIST_HEAD(&dquot->dq_hash); dquot->dq_count = 1; /* all dquots go on the inuse_list */ put_inuse(dquot); return dquot; } static struct dquot *dqget(struct super_block *sb, unsigned int id, short type) { unsigned int hashent = hashfn(sb->s_dev, id, type); struct dquot *dquot, *empty = NODQUOT; struct quota_mount_options *dqopt = sb_dqopt(sb); we_slept: if (!is_enabled(dqopt, type)) { if (empty) dqput(empty); return NODQUOT; } if ((dquot = find_dquot(hashent, sb->s_dev, id, type)) == NODQUOT) { if (empty == NODQUOT) { if ((empty = get_empty_dquot()) == NODQUOT) schedule(); /* Try to wait for a moment... */ goto we_slept; } dquot = empty; dquot->dq_id = id; dquot->dq_type = type; dquot->dq_dev = sb->s_dev; dquot->dq_sb = sb; /* hash it first so it can be found */ insert_dquot_hash(dquot); read_dquot(dquot); } else { if (!dquot->dq_count++) remove_free_dquot(dquot); dqstats.cache_hits++; wait_on_dquot(dquot); if (empty) dqput(empty); } if (!dquot->dq_sb) { /* Has somebody invalidated entry under us? */ printk(KERN_ERR "VFS: dqget(): Quota invalidated in dqget()!\n"); dqput(dquot); return NODQUOT; } dquot->dq_referenced++; dqstats.lookups++; return dquot; } static struct dquot *dqduplicate(struct dquot *dquot) { if (dquot == NODQUOT) return NODQUOT; dquot->dq_count++; if (!dquot->dq_sb) { printk(KERN_ERR "VFS: dqduplicate(): Invalidated quota to be duplicated!\n"); dquot->dq_count--; return NODQUOT; } if (dquot->dq_flags & DQ_LOCKED) printk(KERN_ERR "VFS: dqduplicate(): Locked quota to be duplicated!\n"); dquot->dq_referenced++; dqstats.lookups++; return dquot; } static int dqinit_needed(struct inode *inode, short type) { int cnt; if (IS_NOQUOTA(inode)) return 0; if (type != -1) return inode->i_dquot[type] == NODQUOT; for (cnt = 0; cnt < MAXQUOTAS; cnt++) if (inode->i_dquot[cnt] == NODQUOT) return 1; return 0; } static void add_dquot_ref(struct super_block *sb, short type) { struct list_head *p; if (!sb->dq_op) return; /* nothing to do */ restart: file_list_lock(); for (p = sb->s_files.next; p != &sb->s_files; p = p->next) { struct file *filp = list_entry(p, struct file, f_list); struct inode *inode = filp->f_dentry->d_inode; if (filp->f_mode & FMODE_WRITE && dqinit_needed(inode, type)) { struct vfsmount *mnt = mntget(filp->f_vfsmnt); struct dentry *dentry = dget(filp->f_dentry); file_list_unlock(); sb->dq_op->initialize(inode, type); dput(dentry); mntput(mnt); /* As we may have blocked we had better restart... */ goto restart; } } file_list_unlock(); } /* Return 0 if dqput() won't block (note that 1 doesn't necessarily mean blocking) */ static inline int dqput_blocks(struct dquot *dquot) { if (dquot->dq_count == 1) return 1; return 0; } /* Remove references to dquots from inode - add dquot to list for freeing if needed */ int remove_inode_dquot_ref(struct inode *inode, short type, struct list_head *tofree_head) { struct dquot *dquot = inode->i_dquot[type]; int cnt; inode->i_dquot[type] = NODQUOT; /* any other quota in use? */ for (cnt = 0; cnt < MAXQUOTAS; cnt++) { if (inode->i_dquot[cnt] != NODQUOT) goto put_it; } inode->i_flags &= ~S_QUOTA; put_it: if (dquot != NODQUOT) { if (dqput_blocks(dquot)) { if (dquot->dq_count != 1) printk(KERN_WARNING "VFS: Adding dquot with dq_count %d to dispose list.\n", dquot->dq_count); list_add(&dquot->dq_free, tofree_head); /* As dquot must have currently users it can't be on the free list... */ return 1; } else dqput(dquot); /* We have guaranteed we won't block */ } return 0; } /* Free list of dquots - called from inode.c */ void put_dquot_list(struct list_head *tofree_head) { struct list_head *act_head; struct dquot *dquot; lock_kernel(); act_head = tofree_head->next; /* So now we have dquots on the list... Just free them */ while (act_head != tofree_head) { dquot = list_entry(act_head, struct dquot, dq_free); act_head = act_head->next; list_del(&dquot->dq_free); /* Remove dquot from the list so we won't have problems... */ INIT_LIST_HEAD(&dquot->dq_free); dqput(dquot); } unlock_kernel(); } static inline void dquot_incr_inodes(struct dquot *dquot, unsigned long number) { dquot->dq_curinodes += number; dquot->dq_flags |= DQ_MOD; } static inline void dquot_incr_blocks(struct dquot *dquot, unsigned long number) { dquot->dq_curblocks += number; dquot->dq_flags |= DQ_MOD; } static inline void dquot_decr_inodes(struct dquot *dquot, unsigned long number) { if (dquot->dq_curinodes > number) dquot->dq_curinodes -= number; else dquot->dq_curinodes = 0; if (dquot->dq_curinodes < dquot->dq_isoftlimit) dquot->dq_itime = (time_t) 0; dquot->dq_flags &= ~DQ_INODES; dquot->dq_flags |= DQ_MOD; } static inline void dquot_decr_blocks(struct dquot *dquot, unsigned long number) { if (dquot->dq_curblocks > number) dquot->dq_curblocks -= number; else dquot->dq_curblocks = 0; if (dquot->dq_curblocks < dquot->dq_bsoftlimit) dquot->dq_btime = (time_t) 0; dquot->dq_flags &= ~DQ_BLKS; dquot->dq_flags |= DQ_MOD; } static inline int need_print_warning(struct dquot *dquot, int flag) { switch (dquot->dq_type) { case USRQUOTA: return current->fsuid == dquot->dq_id && !(dquot->dq_flags & flag); case GRPQUOTA: return in_group_p(dquot->dq_id) && !(dquot->dq_flags & flag); } return 0; } /* Values of warnings */ #define NOWARN 0 #define IHARDWARN 1 #define ISOFTLONGWARN 2 #define ISOFTWARN 3 #define BHARDWARN 4 #define BSOFTLONGWARN 5 #define BSOFTWARN 6 /* Print warning to user which exceeded quota */ static void print_warning(struct dquot *dquot, const char warntype) { char *msg = NULL; int flag = (warntype == BHARDWARN || warntype == BSOFTLONGWARN) ? DQ_BLKS : ((warntype == IHARDWARN || warntype == ISOFTLONGWARN) ? DQ_INODES : 0); if (!need_print_warning(dquot, flag)) return; dquot->dq_flags |= flag; tty_write_message(current->tty, (char *)bdevname(dquot->dq_sb->s_dev)); if (warntype == ISOFTWARN || warntype == BSOFTWARN) tty_write_message(current->tty, ": warning, "); else tty_write_message(current->tty, ": write failed, "); tty_write_message(current->tty, quotatypes[dquot->dq_type]); switch (warntype) { case IHARDWARN: msg = " file limit reached.\n"; break; case ISOFTLONGWARN: msg = " file quota exceeded too long.\n"; break; case ISOFTWARN: msg = " file quota exceeded.\n"; break; case BHARDWARN: msg = " block limit reached.\n"; break; case BSOFTLONGWARN: msg = " block quota exceeded too long.\n"; break; case BSOFTWARN: msg = " block quota exceeded.\n"; break; } tty_write_message(current->tty, msg); } static inline void flush_warnings(struct dquot **dquots, char *warntype) { int i; for (i = 0; i < MAXQUOTAS; i++) if (dquots[i] != NODQUOT && warntype[i] != NOWARN) print_warning(dquots[i], warntype[i]); } static inline char ignore_hardlimit(struct dquot *dquot) { return capable(CAP_SYS_RESOURCE) && !dquot->dq_sb->s_dquot.rsquash[dquot->dq_type]; } static int check_idq(struct dquot *dquot, ulong inodes, char *warntype) { *warntype = NOWARN; if (inodes <= 0 || dquot->dq_flags & DQ_FAKE) return QUOTA_OK; if (dquot->dq_ihardlimit && (dquot->dq_curinodes + inodes) > dquot->dq_ihardlimit && !ignore_hardlimit(dquot)) { *warntype = IHARDWARN; return NO_QUOTA; } if (dquot->dq_isoftlimit && (dquot->dq_curinodes + inodes) > dquot->dq_isoftlimit && dquot->dq_itime && CURRENT_TIME >= dquot->dq_itime && !ignore_hardlimit(dquot)) { *warntype = ISOFTLONGWARN; return NO_QUOTA; } if (dquot->dq_isoftlimit && (dquot->dq_curinodes + inodes) > dquot->dq_isoftlimit && dquot->dq_itime == 0) { *warntype = ISOFTWARN; dquot->dq_itime = CURRENT_TIME + dquot->dq_sb->s_dquot.inode_expire[dquot->dq_type]; } return QUOTA_OK; } static int check_bdq(struct dquot *dquot, ulong blocks, char prealloc, char *warntype) { *warntype = 0; if (blocks <= 0 || dquot->dq_flags & DQ_FAKE) return QUOTA_OK; if (dquot->dq_bhardlimit && (dquot->dq_curblocks + blocks) > dquot->dq_bhardlimit && !ignore_hardlimit(dquot)) { if (!prealloc) *warntype = BHARDWARN; return NO_QUOTA; } if (dquot->dq_bsoftlimit && (dquot->dq_curblocks + blocks) > dquot->dq_bsoftlimit && dquot->dq_btime && CURRENT_TIME >= dquot->dq_btime && !ignore_hardlimit(dquot)) { if (!prealloc) *warntype = BSOFTLONGWARN; return NO_QUOTA; } if (dquot->dq_bsoftlimit && (dquot->dq_curblocks + blocks) > dquot->dq_bsoftlimit && dquot->dq_btime == 0) { if (!prealloc) { *warntype = BSOFTWARN; dquot->dq_btime = CURRENT_TIME + dquot->dq_sb->s_dquot.block_expire[dquot->dq_type]; } else /* * We don't allow preallocation to exceed softlimit so exceeding will * be always printed */ return NO_QUOTA; } return QUOTA_OK; } /* * Initialize a dquot-struct with new quota info. This is used by the * system call interface functions. */ static int set_dqblk(struct super_block *sb, int id, short type, int flags, struct dqblk *dqblk) { struct dquot *dquot; int error = -EFAULT; struct dqblk dq_dqblk; if (copy_from_user(&dq_dqblk, dqblk, sizeof(struct dqblk))) return error; if (sb && (dquot = dqget(sb, id, type)) != NODQUOT) { /* We can't block while changing quota structure... */ if (id > 0 && ((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 & SET_QUOTA) || (flags & SET_USE)) { if (dquot->dq_isoftlimit && dquot->dq_curinodes < dquot->dq_isoftlimit && dq_dqblk.dqb_curinodes >= dquot->dq_isoftlimit) dquot->dq_itime = CURRENT_TIME + dquot->dq_sb->s_dquot.inode_expire[type]; dquot->dq_curinodes = dq_dqblk.dqb_curinodes; if (dquot->dq_curinodes < dquot->dq_isoftlimit) dquot->dq_flags &= ~DQ_INODES; if (dquot->dq_bsoftlimit && dquot->dq_curblocks < dquot->dq_bsoftlimit && dq_dqblk.dqb_curblocks >= dquot->dq_bsoftlimit) dquot->dq_btime = CURRENT_TIME + dquot->dq_sb->s_dquot.block_expire[type]; dquot->dq_curblocks = dq_dqblk.dqb_curblocks; if (dquot->dq_curblocks < dquot->dq_bsoftlimit) dquot->dq_flags &= ~DQ_BLKS; } if (id == 0) { dquot->dq_sb->s_dquot.block_expire[type] = dquot->dq_btime = dq_dqblk.dqb_btime; dquot->dq_sb->s_dquot.inode_expire[type] = dquot->dq_itime = dq_dqblk.dqb_itime; } if (dq_dqblk.dqb_bhardlimit == 0 && dq_dqblk.dqb_bsoftlimit == 0 && dq_dqblk.dqb_ihardlimit == 0 && dq_dqblk.dqb_isoftlimit == 0) dquot->dq_flags |= DQ_FAKE; else dquot->dq_flags &= ~DQ_FAKE; dquot->dq_flags |= DQ_MOD; dqput(dquot); } return 0; } static int get_quota(struct super_block *sb, int id, short type, struct dqblk *dqblk) { struct dquot *dquot; struct dqblk data; int error = -ESRCH; if (!sb || !sb_has_quota_enabled(sb, type)) goto out; dquot = dqget(sb, id, type); if (dquot == NODQUOT) goto out; memcpy(&data, &dquot->dq_dqb, sizeof(struct dqblk)); /* We copy data to preserve them from changing */ dqput(dquot); error = -EFAULT; if (dqblk && !copy_to_user(dqblk, &data, sizeof(struct dqblk))) error = 0; out: return error; } static int get_stats(caddr_t addr) { int error = -EFAULT; struct dqstats stats; dqstats.allocated_dquots = nr_dquots; dqstats.free_dquots = nr_free_dquots; /* make a copy, in case we page-fault in user space */ memcpy(&stats, &dqstats, sizeof(struct dqstats)); if (!copy_to_user(addr, &stats, sizeof(struct dqstats))) error = 0; return error; } static int quota_root_squash(struct super_block *sb, short type, int *addr) { int new_value, error; if (!sb) return(-ENODEV); error = -EFAULT; if (!copy_from_user(&new_value, addr, sizeof(int))) { sb_dqopt(sb)->rsquash[type] = new_value; error = 0; } return error; } #if 0 /* We are not going to support filesystems without i_blocks... */ /* * This is a simple algorithm that calculates the size of a file in blocks. * This is only used on filesystems that do not have an i_blocks count. */ static u_long isize_to_blocks(loff_t isize, size_t blksize_bits) { u_long blocks; u_long indirect; if (!blksize_bits) blksize_bits = BLOCK_SIZE_BITS; blocks = (isize >> blksize_bits) + ((isize & ~((1 << blksize_bits)-1)) ? 1 : 0); if (blocks > 10) { indirect = ((blocks - 11) >> 8) + 1; /* single indirect blocks */ if (blocks > (10 + 256)) { indirect += ((blocks - 267) >> 16) + 1; /* double indirect blocks */ if (blocks > (10 + 256 + (256 << 8))) indirect++; /* triple indirect blocks */ } blocks += indirect; } return blocks; } #endif /* * Externally referenced functions through dquot_operations in inode. * * Note: this is a blocking operation. */ void dquot_initialize(struct inode *inode, short type) { struct dquot *dquot[MAXQUOTAS]; unsigned int id = 0; short cnt; if (IS_NOQUOTA(inode)) return; /* Build list of quotas to initialize... We can block here */ for (cnt = 0; cnt < MAXQUOTAS; cnt++) { dquot[cnt] = NODQUOT; if (type != -1 && cnt != type) continue; if (!sb_has_quota_enabled(inode->i_sb, cnt)) continue; if (inode->i_dquot[cnt] == NODQUOT) { switch (cnt) { case USRQUOTA: id = inode->i_uid; break; case GRPQUOTA: id = inode->i_gid; break; } dquot[cnt] = dqget(inode->i_sb, id, cnt); } } /* NOBLOCK START: Here we shouldn't block */ for (cnt = 0; cnt < MAXQUOTAS; cnt++) { if (dquot[cnt] == NODQUOT || !sb_has_quota_enabled(inode->i_sb, cnt) || inode->i_dquot[cnt] != NODQUOT) continue; inode->i_dquot[cnt] = dquot[cnt]; dquot[cnt] = NODQUOT; inode->i_flags |= S_QUOTA; } /* NOBLOCK END */ /* Put quotas which we didn't use */ for (cnt = 0; cnt < MAXQUOTAS; cnt++) if (dquot[cnt] != NODQUOT) dqput(dquot[cnt]); } /* * Release all quota for the specified inode. * * Note: this is a blocking operation. */ void dquot_drop(struct inode *inode) { struct dquot *dquot; short cnt; inode->i_flags &= ~S_QUOTA; for (cnt = 0; cnt < MAXQUOTAS; cnt++) { if (inode->i_dquot[cnt] == NODQUOT) continue; dquot = inode->i_dquot[cnt]; inode->i_dquot[cnt] = NODQUOT; dqput(dquot); } } /* * This operation can block, but only after everything is updated */ int dquot_alloc_block(struct inode *inode, unsigned long number, char warn) { int cnt, ret = NO_QUOTA; struct dquot *dquot[MAXQUOTAS]; char warntype[MAXQUOTAS]; for (cnt = 0; cnt < MAXQUOTAS; cnt++) { dquot[cnt] = NODQUOT; warntype[cnt] = NOWARN; } /* NOBLOCK Start */ for (cnt = 0; cnt < MAXQUOTAS; cnt++) { dquot[cnt] = dqduplicate(inode->i_dquot[cnt]); if (dquot[cnt] == NODQUOT) continue; if (check_bdq(dquot[cnt], number, warn, warntype+cnt) == NO_QUOTA) goto warn_put_all; } for (cnt = 0; cnt < MAXQUOTAS; cnt++) { if (dquot[cnt] == NODQUOT) continue; dquot_incr_blocks(dquot[cnt], number); } inode->i_blocks += number << (BLOCK_SIZE_BITS - 9); /* NOBLOCK End */ ret = QUOTA_OK; warn_put_all: flush_warnings(dquot, warntype); for (cnt = 0; cnt < MAXQUOTAS; cnt++) if (dquot[cnt] != NODQUOT) dqput(dquot[cnt]); return ret; } /* * This operation can block, but only after everything is updated */ int dquot_alloc_inode(const struct inode *inode, unsigned long number) { int cnt, ret = NO_QUOTA; struct dquot *dquot[MAXQUOTAS]; char warntype[MAXQUOTAS]; for (cnt = 0; cnt < MAXQUOTAS; cnt++) { dquot[cnt] = NODQUOT; warntype[cnt] = NOWARN; } /* NOBLOCK Start */ for (cnt = 0; cnt < MAXQUOTAS; cnt++) { dquot[cnt] = dqduplicate(inode -> i_dquot[cnt]); if (dquot[cnt] == NODQUOT) continue; if (check_idq(dquot[cnt], number, warntype+cnt) == NO_QUOTA) goto warn_put_all; } for (cnt = 0; cnt < MAXQUOTAS; cnt++) { if (dquot[cnt] == NODQUOT) continue; dquot_incr_inodes(dquot[cnt], number); } /* NOBLOCK End */ ret = QUOTA_OK; warn_put_all: flush_warnings(dquot, warntype); for (cnt = 0; cnt < MAXQUOTAS; cnt++) if (dquot[cnt] != NODQUOT) dqput(dquot[cnt]); return ret; } /* * This is a non-blocking operation. */ void dquot_free_block(struct inode *inode, unsigned long number) { unsigned short cnt; struct dquot *dquot; /* NOBLOCK Start */ for (cnt = 0; cnt < MAXQUOTAS; cnt++) { dquot = dqduplicate(inode->i_dquot[cnt]); if (dquot == NODQUOT) continue; dquot_decr_blocks(dquot, number); dqput(dquot); } inode->i_blocks -= number << (BLOCK_SIZE_BITS - 9); /* NOBLOCK End */ } /* * This is a non-blocking operation. */ void dquot_free_inode(const struct inode *inode, unsigned long number) { unsigned short cnt; struct dquot *dquot; /* NOBLOCK Start */ for (cnt = 0; cnt < MAXQUOTAS; cnt++) { dquot = dqduplicate(inode->i_dquot[cnt]); if (dquot == NODQUOT) continue; dquot_decr_inodes(dquot, number); dqput(dquot); } /* NOBLOCK End */ } /* * Transfer the number of inode and blocks from one diskquota to an other. * * This operation can block, but only after everything is updated */ int dquot_transfer(struct inode *inode, struct iattr *iattr) { unsigned long blocks; struct dquot *transfer_from[MAXQUOTAS]; struct dquot *transfer_to[MAXQUOTAS]; int cnt, ret = NO_QUOTA, chuid = (iattr->ia_valid & ATTR_UID) && inode->i_uid != iattr->ia_uid, chgid = (iattr->ia_valid & ATTR_GID) && inode->i_gid != iattr->ia_gid; char warntype[MAXQUOTAS]; /* Clear the arrays */ for (cnt = 0; cnt < MAXQUOTAS; cnt++) { transfer_to[cnt] = transfer_from[cnt] = NODQUOT; warntype[cnt] = NOWARN; } /* First build the transfer_to list - here we can block on reading of dquots... */ for (cnt = 0; cnt < MAXQUOTAS; cnt++) { if (!sb_has_quota_enabled(inode->i_sb, cnt)) continue; switch (cnt) { case USRQUOTA: if (!chuid) continue; transfer_to[cnt] = dqget(inode->i_sb, iattr->ia_uid, cnt); break; case GRPQUOTA: if (!chgid) continue; transfer_to[cnt] = dqget(inode->i_sb, iattr->ia_gid, cnt); break; } } /* NOBLOCK START: From now on we shouldn't block */ blocks = (inode->i_blocks >> 1); /* Build the transfer_from list and check the limits */ for (cnt = 0; cnt < MAXQUOTAS; cnt++) { /* The second test can fail when quotaoff is in progress... */ if (transfer_to[cnt] == NODQUOT || !sb_has_quota_enabled(inode->i_sb, cnt)) continue; transfer_from[cnt] = dqduplicate(inode->i_dquot[cnt]); if (transfer_from[cnt] == NODQUOT) /* Can happen on quotafiles (quota isn't initialized on them)... */ continue; if (check_idq(transfer_to[cnt], 1, warntype+cnt) == NO_QUOTA || check_bdq(transfer_to[cnt], blocks, 0, warntype+cnt) == NO_QUOTA) goto warn_put_all; } /* * Finally perform the needed transfer from transfer_from to transfer_to */ for (cnt = 0; cnt < MAXQUOTAS; cnt++) { /* * Skip changes for same uid or gid or for non-existing quota-type. */ if (transfer_from[cnt] == NODQUOT || transfer_to[cnt] == NODQUOT) continue; dquot_decr_inodes(transfer_from[cnt], 1); dquot_decr_blocks(transfer_from[cnt], blocks); dquot_incr_inodes(transfer_to[cnt], 1); dquot_incr_blocks(transfer_to[cnt], blocks); if (inode->i_dquot[cnt] == NODQUOT) BUG(); inode->i_dquot[cnt] = transfer_to[cnt]; /* * We've got to release transfer_from[] twice - once for dquot_transfer() and * once for inode. We don't want to release transfer_to[] as it's now placed in inode */ transfer_to[cnt] = transfer_from[cnt]; } /* NOBLOCK END. From now on we can block as we wish */ ret = QUOTA_OK; warn_put_all: flush_warnings(transfer_to, warntype); for (cnt = 0; cnt < MAXQUOTAS; cnt++) { if (transfer_to[cnt] != NODQUOT) dqput(transfer_to[cnt]); if (transfer_from[cnt] != NODQUOT) dqput(transfer_from[cnt]); } return ret; } static int __init dquot_init(void) { int i; for (i = 0; i < NR_DQHASH; i++) INIT_LIST_HEAD(dquot_hash + i); printk(KERN_NOTICE "VFS: Diskquotas version %s initialized\n", __DQUOT_VERSION__); return 0; } __initcall(dquot_init); /* * Definitions of diskquota operations. */ struct dquot_operations dquot_operations = { dquot_initialize, /* mandatory */ dquot_drop, /* mandatory */ dquot_alloc_block, dquot_alloc_inode, dquot_free_block, dquot_free_inode, dquot_transfer }; static inline void set_enable_flags(struct quota_mount_options *dqopt, short type) { switch (type) { case USRQUOTA: dqopt->flags |= DQUOT_USR_ENABLED; break; case GRPQUOTA: dqopt->flags |= DQUOT_GRP_ENABLED; break; } } static inline void reset_enable_flags(struct quota_mount_options *dqopt, short type) { switch (type) { case USRQUOTA: dqopt->flags &= ~DQUOT_USR_ENABLED; break; case GRPQUOTA: dqopt->flags &= ~DQUOT_GRP_ENABLED; break; } } /* Function in inode.c - remove pointers to dquots in icache */ extern void remove_dquot_ref(struct super_block *, short); /* * Turn quota off on a device. type == -1 ==> quotaoff for all types (umount) */ int quota_off(struct super_block *sb, short type) { struct file *filp; short cnt; struct quota_mount_options *dqopt = sb_dqopt(sb); lock_kernel(); if (!sb) goto out; /* We need to serialize quota_off() for device */ down(&dqopt->dqoff_sem); for (cnt = 0; cnt < MAXQUOTAS; cnt++) { if (type != -1 && cnt != type) continue; if (!is_enabled(dqopt, cnt)) continue; reset_enable_flags(dqopt, cnt); /* Note: these are blocking operations */ remove_dquot_ref(sb, cnt); invalidate_dquots(sb, cnt); filp = dqopt->files[cnt]; dqopt->files[cnt] = (struct file *)NULL; dqopt->inode_expire[cnt] = 0; dqopt->block_expire[cnt] = 0; fput(filp); } up(&dqopt->dqoff_sem); out: unlock_kernel(); return 0; } static inline int check_quotafile_size(loff_t size) { ulong blocks = size >> BLOCK_SIZE_BITS; size_t off = size & (BLOCK_SIZE - 1); return !(((blocks % sizeof(struct dqblk)) * BLOCK_SIZE + off % sizeof(struct dqblk)) % sizeof(struct dqblk)); } static int quota_on(struct super_block *sb, short type, char *path) { struct file *f; struct inode *inode; struct dquot *dquot; struct quota_mount_options *dqopt = sb_dqopt(sb); char *tmp; int error; if (is_enabled(dqopt, type)) return -EBUSY; down(&dqopt->dqoff_sem); tmp = getname(path); error = PTR_ERR(tmp); if (IS_ERR(tmp)) goto out_lock; f = filp_open(tmp, O_RDWR, 0600); putname(tmp); error = PTR_ERR(f); if (IS_ERR(f)) goto out_lock; error = -EIO; if (!f->f_op || !f->f_op->read || !f->f_op->write) goto out_f; inode = f->f_dentry->d_inode; error = -EACCES; if (!S_ISREG(inode->i_mode)) goto out_f; error = -EINVAL; if (inode->i_size == 0 || !check_quotafile_size(inode->i_size)) goto out_f; /* We don't want quota on quota files */ dquot_drop(inode); inode->i_flags |= S_NOQUOTA; dqopt->files[type] = f; sb->dq_op = &dquot_operations; set_enable_flags(dqopt, type); dquot = dqget(sb, 0, type); dqopt->inode_expire[type] = (dquot != NODQUOT) ? dquot->dq_itime : MAX_IQ_TIME; dqopt->block_expire[type] = (dquot != NODQUOT) ? dquot->dq_btime : MAX_DQ_TIME; dqput(dquot); add_dquot_ref(sb, type); up(&dqopt->dqoff_sem); return 0; out_f: filp_close(f, NULL); out_lock: up(&dqopt->dqoff_sem); return error; } /* * This is the system call interface. This communicates with * the user-level programs. Currently this only supports diskquota * calls. Maybe we need to add the process quotas etc. in the future, * but we probably should use rlimits for that. */ asmlinkage long sys_quotactl(int cmd, const char *special, int id, caddr_t addr) { int cmds = 0, type = 0, flags = 0; kdev_t dev; struct super_block *sb = NULL; int ret = -EINVAL; lock_kernel(); cmds = cmd >> SUBCMDSHIFT; type = cmd & SUBCMDMASK; if ((u_int) type >= MAXQUOTAS) goto out; if (id & ~0xFFFF) goto out; ret = -EPERM; switch (cmds) { case Q_SYNC: case Q_GETSTATS: break; case Q_GETQUOTA: if (((type == USRQUOTA && current->euid != id) || (type == GRPQUOTA && !in_egroup_p(id))) && !capable(CAP_SYS_ADMIN)) goto out; break; default: if (!capable(CAP_SYS_ADMIN)) goto out; } ret = -EINVAL; dev = NODEV; if (special != NULL || (cmds != Q_SYNC && cmds != Q_GETSTATS)) { mode_t mode; struct nameidata nd; ret = user_path_walk(special, &nd); if (ret) goto out; dev = nd.dentry->d_inode->i_rdev; mode = nd.dentry->d_inode->i_mode; path_release(&nd); ret = -ENOTBLK; if (!S_ISBLK(mode)) goto out; ret = -ENODEV; sb = get_super(dev); if (!sb) goto out; } ret = -EINVAL; switch (cmds) { case Q_QUOTAON: ret = quota_on(sb, type, (char *) addr); goto out; case Q_QUOTAOFF: ret = quota_off(sb, type); goto out; case Q_GETQUOTA: ret = get_quota(sb, id, type, (struct dqblk *) addr); goto out; case Q_SETQUOTA: flags |= SET_QUOTA; break; case Q_SETUSE: flags |= SET_USE; break; case Q_SETQLIM: flags |= SET_QLIMIT; break; case Q_SYNC: ret = sync_dquots(dev, type); goto out; case Q_GETSTATS: ret = get_stats(addr); goto out; case Q_RSQUASH: ret = quota_root_squash(sb, type, (int *) addr); goto out; default: goto out; } ret = -NODEV; if (sb && sb_has_quota_enabled(sb, type)) ret = set_dqblk(sb, id, type, flags, (struct dqblk *) addr); out: if (sb) drop_super(sb); unlock_kernel(); return ret; }