📄 dquot.c
字号:
/* * 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 <mvw@planets.elm.net> * * Fixes: Dmitry Gorodchanin <pgmdsg@ibi.com>, 11 Feb 96 * * Revised list management to avoid races * -- Bill Hawes, <whawes@star.net>, 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, <jack@suse.cz>, 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, <jack@suse.cz>, 9/2000 * * Added dynamic quota structure allocation * Jan Kara <jack@suse.cz> 12/2000 * * (C) Copyright 1994 - 1997 Marco van Wieringen */#include <linux/errno.h>#include <linux/kernel.h>#include <linux/fs.h>#include <linux/sched.h>#include <linux/types.h>#include <linux/string.h>#include <linux/fcntl.h>#include <linux/stat.h>#include <linux/tty.h>#include <linux/file.h>#include <linux/slab.h>#include <linux/smp_lock.h>#include <linux/init.h>#include <asm/uaccess.h>#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)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -