diff options
author | Ralf Baechle <ralf@linux-mips.org> | 2000-02-04 07:40:19 +0000 |
---|---|---|
committer | Ralf Baechle <ralf@linux-mips.org> | 2000-02-04 07:40:19 +0000 |
commit | 33263fc5f9ac8e8cb2b22d06af3ce5ac1dd815e4 (patch) | |
tree | 2d1b86a40bef0958a68cf1a2eafbeb0667a70543 /fs/dquot.c | |
parent | 216f5f51aa02f8b113aa620ebc14a9631a217a00 (diff) |
Merge with Linux 2.3.32.
Diffstat (limited to 'fs/dquot.c')
-rw-r--r-- | fs/dquot.c | 723 |
1 files changed, 461 insertions, 262 deletions
diff --git a/fs/dquot.c b/fs/dquot.c index 2317af965..1efafcf51 100644 --- a/fs/dquot.c +++ b/fs/dquot.c @@ -21,6 +21,19 @@ * 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 + * * (C) Copyright 1994 - 1997 Marco van Wieringen */ @@ -74,7 +87,7 @@ static kmem_cache_t *dquot_cachep; */ static struct dquot *inuse_list = NULL; -LIST_HEAD(free_dquots); +static LIST_HEAD(free_dquots); static struct dquot *dquot_hash[NR_DQHASH]; static int dquot_updating[NR_DQHASH]; @@ -82,6 +95,9 @@ static struct dqstats dqstats; static DECLARE_WAIT_QUEUE_HEAD(dquot_wait); static DECLARE_WAIT_QUEUE_HEAD(update_wait); +static void dqput(struct dquot *); +static struct dquot *dqduplicate(struct dquot *); + static inline char is_enabled(struct vfsmount *vfsmnt, short type) { switch (type) { @@ -166,7 +182,8 @@ static inline void remove_free_dquot(struct dquot *dquot) { /* sanity check */ if (list_empty(&dquot->dq_free)) { - printk("remove_free_dquot: dquot not on free list??\n"); + printk("remove_free_dquot: dquot not on the free list??\n"); + return; /* J.K. Just don't do anything */ } list_del(&dquot->dq_free); INIT_LIST_HEAD(&dquot->dq_free); @@ -226,16 +243,25 @@ static inline void unlock_dquot(struct dquot *dquot) wake_up(&dquot->dq_wait); } +/* + * 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 = dquot->dq_mnt->mnt_dquot.files[type]; + struct file *filp; mm_segment_t fs; loff_t offset; ssize_t ret; + struct semaphore *sem = &dquot->dq_mnt->mnt_dquot.dqio_sem; lock_dquot(dquot); - down(&dquot->dq_mnt->mnt_dquot.semaphore); + if (!dquot->dq_mnt) { /* Invalidated quota? */ + unlock_dquot(dquot); + return; + } + down(sem); + filp = dquot->dq_mnt->mnt_dquot.files[type]; offset = dqoff(dquot->dq_id); fs = get_fs(); set_fs(KERNEL_DS); @@ -253,40 +279,42 @@ static void write_dquot(struct dquot *dquot) printk(KERN_WARNING "VFS: dquota write failed on dev %s\n", kdevname(dquot->dq_dev)); - up(&dquot->dq_mnt->mnt_dquot.semaphore); set_fs(fs); - + up(sem); unlock_dquot(dquot); + dqstats.writes++; } static void read_dquot(struct dquot *dquot) { - short type; + short type = dquot->dq_type; struct file *filp; mm_segment_t fs; loff_t offset; - type = dquot->dq_type; filp = dquot->dq_mnt->mnt_dquot.files[type]; - if (filp == (struct file *)NULL) return; lock_dquot(dquot); - down(&dquot->dq_mnt->mnt_dquot.semaphore); + if (!dquot->dq_mnt) /* Invalidated quota? */ + goto out_lock; + /* Now we are sure filp is valid - the dquot isn't invalidated */ + down(&dquot->dq_mnt->mnt_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_mnt->mnt_dquot.semaphore); + up(&dquot->dq_mnt->mnt_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; - unlock_dquot(dquot); dqstats.reads++; +out_lock: + unlock_dquot(dquot); } /* @@ -306,10 +334,11 @@ void clear_dquot(struct dquot *dquot) void invalidate_dquots(kdev_t dev, short type) { - struct dquot *dquot, *next = inuse_list; + struct dquot *dquot, *next; int need_restart; restart: + next = inuse_list; /* Here it is better. Otherwise the restart doesn't have any sense ;-) */ need_restart = 0; while ((dquot = next) != NULL) { next = dquot->dq_next; @@ -317,6 +346,8 @@ restart: continue; if (dquot->dq_type != type) continue; + if (!dquot->dq_mnt) /* Already invalidated entry? */ + continue; if (dquot->dq_flags & DQ_LOCKED) { __wait_on_dquot(dquot); @@ -329,6 +360,18 @@ restart: continue; if (dquot->dq_type != type) continue; + if (!dquot->dq_mnt) + continue; + } + /* + * Because inodes needn't to be the only holders of dquot + * the quota needn't to be written to disk. So we write it + * ourselves before discarding the data just for sure... + */ + if (dquot->dq_flags & DQ_MOD && dquot->dq_mnt) + { + write_dquot(dquot); + need_restart = 1; /* We slept on IO */ } clear_dquot(dquot); } @@ -342,10 +385,11 @@ restart: int sync_dquots(kdev_t dev, short type) { - struct dquot *dquot, *next = inuse_list; + struct dquot *dquot, *next, *ddquot; int need_restart; restart: + next = inuse_list; need_restart = 0; while ((dquot = next) != NULL) { next = dquot->dq_next; @@ -353,12 +397,16 @@ restart: continue; if (type != -1 && dquot->dq_type != type) continue; + if (!dquot->dq_mnt) /* Invalidated? */ + continue; if (!(dquot->dq_flags & (DQ_LOCKED | DQ_MOD))) continue; - wait_on_dquot(dquot); - if (dquot->dq_flags & DQ_MOD) - write_dquot(dquot); + if ((ddquot = dqduplicate(dquot)) == NODQUOT) + continue; + if (ddquot->dq_flags & DQ_MOD) + write_dquot(ddquot); + dqput(ddquot); /* Set the flag for another pass. */ need_restart = 1; } @@ -373,7 +421,8 @@ restart: return(0); } -void dqput(struct dquot *dquot) +/* NOTE: If you change this function please check whether dqput_blocks() works right... */ +static void dqput(struct dquot *dquot) { if (!dquot) return; @@ -390,42 +439,45 @@ void dqput(struct dquot *dquot) * checking and doesn't need to be written. It's just an empty * dquot that is put back on to the freelist. */ - if (dquot->dq_mnt != (struct vfsmount *)NULL) { + if (dquot->dq_mnt) dqstats.drops++; we_slept: - wait_on_dquot(dquot); - if (dquot->dq_count > 1) { - dquot->dq_count--; - return; - } - if (dquot->dq_flags & DQ_MOD) { - write_dquot(dquot); - goto 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_LOCKED) { + printk(KERN_ERR "VFS: Locked quota to be put on the free list.\n"); + dquot->dq_flags &= ~DQ_LOCKED; + } + if (dquot->dq_mnt && dquot->dq_flags & DQ_MOD) { + write_dquot(dquot); + goto we_slept; } /* sanity check */ if (!list_empty(&dquot->dq_free)) { - printk("dqput: dquot already on free list??\n"); - } - if (--dquot->dq_count == 0) { - /* Place at end of LRU free queue */ - put_dquot_last(dquot); - wake_up(&dquot_wait); + printk(KERN_ERR "dqput: dquot already on free list??\n"); + dquot->dq_count--; /* J.K. Just decrementing use count seems safer... */ + return; } - - return; + dquot->dq_count--; + dquot->dq_flags &= ~DQ_MOD; /* Modified flag has no sense on free list */ + /* Place at end of LRU free queue */ + put_dquot_last(dquot); + wake_up(&dquot_wait); } -static void grow_dquots(void) +static int grow_dquots(void) { struct dquot *dquot; - int cnt = 32; + int cnt = 0; - while (cnt > 0) { + while (cnt < 32) { dquot = kmem_cache_alloc(dquot_cachep, SLAB_KERNEL); if(!dquot) - return; + return cnt; nr_dquots++; memset((caddr_t)dquot, 0, sizeof(struct dquot)); @@ -433,8 +485,9 @@ static void grow_dquots(void) /* all dquots go on the inuse_list */ put_inuse(dquot); put_dquot_head(dquot); - cnt--; + cnt++; } + return cnt; } static struct dquot *find_best_candidate_weighted(void) @@ -446,6 +499,7 @@ static struct dquot *find_best_candidate_weighted(void) while ((tmp = tmp->next) != &free_dquots && --limit) { dquot = list_entry(tmp, struct dquot, dq_free); + /* This should never happen... */ if (dquot->dq_flags & (DQ_LOCKED | DQ_MOD)) continue; myscore = dquot->dq_referenced; @@ -474,27 +528,16 @@ static inline struct dquot *find_best_free(void) struct dquot *get_empty_dquot(void) { struct dquot *dquot; - int count; + int shrink = 1; /* Number of times we should try to shrink dcache and icache */ repeat: dquot = find_best_free(); if (!dquot) goto pressure; got_it: - if (dquot->dq_flags & (DQ_LOCKED | DQ_MOD)) { - wait_on_dquot(dquot); - if (dquot->dq_flags & DQ_MOD) - { - if(dquot->dq_mnt != (struct vfsmount *)NULL) - write_dquot(dquot); - } - /* - * The dquot may be back in use now, so we - * must recheck the free list. - */ - goto repeat; - } - /* sanity check ... */ + /* Sanity checks */ + if (dquot->dq_flags & DQ_LOCKED) + printk(KERN_ERR "VFS: Locked dquot on the free list\n"); if (dquot->dq_count != 0) printk(KERN_ERR "VFS: free dquot count=%d\n", dquot->dq_count); @@ -505,10 +548,9 @@ got_it: return dquot; pressure: - if (nr_dquots < max_dquots) { - grow_dquots(); - goto repeat; - } + if (nr_dquots < max_dquots) + if (grow_dquots()) + goto repeat; dquot = find_best_candidate_weighted(); if (dquot) @@ -516,10 +558,11 @@ pressure: /* * Try pruning the dcache to free up some dquots ... */ - if (count) { - printk(KERN_DEBUG "get_empty_dquot: pruning %d\n", count); - prune_dcache(count); - prune_icache(count); + if (shrink) { + printk(KERN_DEBUG "get_empty_dquot: pruning dcache and icache\n"); + prune_dcache(128); + prune_icache(128); + shrink--; goto repeat; } @@ -534,7 +577,7 @@ struct dquot *dqget(kdev_t dev, unsigned int id, short type) struct dquot *dquot, *empty = NULL; struct vfsmount *vfsmnt; - if ((vfsmnt = lookup_vfsmnt(dev)) == (struct vfsmount *)NULL || is_enabled(vfsmnt, type) == 0) + if ((vfsmnt = lookup_vfsmnt(dev)) == (struct vfsmount *)NULL || !is_enabled(vfsmnt, type)) return(NODQUOT); we_slept: @@ -567,12 +610,67 @@ we_slept: while (dquot_updating[hashent]) sleep_on(&update_wait); + if (!dquot->dq_mnt) { /* Has somebody invalidated entry under us? */ + /* + * Do it as if the quota was invalidated before we started + */ + dqput(dquot); + return NODQUOT; + } dquot->dq_referenced++; dqstats.lookups++; return dquot; } +static struct dquot *dqduplicate(struct dquot *dquot) +{ + if (dquot == NODQUOT || !dquot->dq_mnt) + return NODQUOT; + dquot->dq_count++; + wait_on_dquot(dquot); + if (!dquot->dq_mnt) { + dquot->dq_count--; + return NODQUOT; + } + dquot->dq_referenced++; + dqstats.lookups++; + return dquot; +} + +/* Check whether this inode is quota file */ +static inline int is_quotafile(struct inode *inode) +{ + int cnt; + struct vfsmount *vfsmnt; + struct file **files; + + vfsmnt = lookup_vfsmnt(inode->i_dev); + if (!vfsmnt) + return 0; + files = vfsmnt->mnt_dquot.files; + for (cnt = 0; cnt < MAXQUOTAS; cnt++) + if (files[cnt] && files[cnt]->f_dentry->d_inode == inode) + return 1; + return 0; +} + +static int dqinit_needed(struct inode *inode, short type) +{ + int cnt; + + if (!(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode) || S_ISLNK(inode->i_mode))) + return 0; + if (is_quotafile(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(kdev_t dev, short type) { struct super_block *sb = get_super(dev); @@ -582,6 +680,7 @@ static void add_dquot_ref(kdev_t dev, short type) if (!sb || !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); @@ -590,84 +689,81 @@ static void add_dquot_ref(kdev_t dev, short type) inode = filp->f_dentry->d_inode; if (!inode) continue; - /* N.B. race problem -- filp could become unused */ - if (filp->f_mode & FMODE_WRITE) { + if (filp->f_mode & FMODE_WRITE && dqinit_needed(inode, type)) { file_list_unlock(); sb->dq_op->initialize(inode, type); inode->i_flags |= S_QUOTA; - file_list_lock(); + /* As we may have blocked we had better restart... */ + goto restart; } } file_list_unlock(); } -static void reset_dquot_ptrs(kdev_t dev, short type) +/* Return 0 if dqput() won't block (note that 1 doesn't necessarily mean blocking) */ +static inline int dqput_blocks(struct dquot *dquot) { - struct super_block *sb = get_super(dev); - struct list_head *p; - struct inode *inode; - 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; - if (!sb || !sb->dq_op) - return; /* nothing to do */ + 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; +} -restart: - /* free any quota for unused dentries */ - shrink_dcache_sb(sb); +/* Free list of dquots - called from inode.c */ +void put_dquot_list(struct list_head *tofree_head) +{ + struct list_head *act_head = tofree_head; + struct dquot *dquot; - 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); - if (!filp->f_dentry) - continue; - inode = filp->f_dentry->d_inode; - if (!inode) - continue; - /* - * Note: we restart after each blocking operation, - * as the inuse_filps list may have changed. - */ - if (IS_QUOTAINIT(inode)) { - dquot = inode->i_dquot[type]; - 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) { - file_list_unlock(); - dqput(dquot); - /* we may have blocked ... */ - goto restart; - } - } + /* 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); } - file_list_unlock(); } static inline void dquot_incr_inodes(struct dquot *dquot, unsigned long number) { - lock_dquot(dquot); dquot->dq_curinodes += number; dquot->dq_flags |= DQ_MOD; - unlock_dquot(dquot); } static inline void dquot_incr_blocks(struct dquot *dquot, unsigned long number) { - lock_dquot(dquot); dquot->dq_curblocks += number; dquot->dq_flags |= DQ_MOD; - unlock_dquot(dquot); } static inline void dquot_decr_inodes(struct dquot *dquot, unsigned long number) { - lock_dquot(dquot); if (dquot->dq_curinodes > number) dquot->dq_curinodes -= number; else @@ -676,12 +772,10 @@ static inline void dquot_decr_inodes(struct dquot *dquot, unsigned long number) dquot->dq_itime = (time_t) 0; dquot->dq_flags &= ~DQ_INODES; dquot->dq_flags |= DQ_MOD; - unlock_dquot(dquot); } static inline void dquot_decr_blocks(struct dquot *dquot, unsigned long number) { - lock_dquot(dquot); if (dquot->dq_curblocks > number) dquot->dq_curblocks -= number; else @@ -690,113 +784,115 @@ static inline void dquot_decr_blocks(struct dquot *dquot, unsigned long number) dquot->dq_btime = (time_t) 0; dquot->dq_flags &= ~DQ_BLKS; dquot->dq_flags |= DQ_MOD; - unlock_dquot(dquot); } -static inline char need_print_warning(short type, uid_t initiator, struct dquot *dquot) +static inline int need_print_warning(struct dquot *dquot, int flag) { - switch (type) { + switch (dquot->dq_type) { case USRQUOTA: - return(initiator == dquot->dq_id); + return current->fsuid == dquot->dq_id && !(dquot->dq_flags & flag); case GRPQUOTA: - return(initiator == dquot->dq_id); + return in_group_p(dquot->dq_id) && !(dquot->dq_flags & flag); } - return(0); + return 0; } -static inline char ignore_hardlimit(struct dquot *dquot, uid_t initiator) +static void print_warning(struct dquot *dquot, int flag, char *fmtstr, ...) { - return(initiator == 0 && dquot->dq_mnt->mnt_dquot.rsquash[dquot->dq_type] == 0); + va_list args; + + if (!need_print_warning(dquot, flag)) + return; + va_start(args, fmtstr); + vsprintf(quotamessage, fmtstr, args); + va_end(args); + tty_write_message(current->tty, quotamessage); + dquot->dq_flags |= flag; } -static int check_idq(struct dquot *dquot, short type, u_long short inodes, uid_t initiator, - struct tty_struct *tty) +static inline char ignore_hardlimit(struct dquot *dquot) { + return capable(CAP_SYS_RESOURCE) && !dquot->dq_mnt->mnt_dquot.rsquash[dquot->dq_type]; +} + +static int check_idq(struct dquot *dquot, u_long inodes) +{ + short type = dquot->dq_type; + if (inodes <= 0 || dquot->dq_flags & DQ_FAKE) - return(QUOTA_OK); + return QUOTA_OK; if (dquot->dq_ihardlimit && (dquot->dq_curinodes + inodes) > dquot->dq_ihardlimit && - !ignore_hardlimit(dquot, initiator)) { - if ((dquot->dq_flags & DQ_INODES) == 0 && - need_print_warning(type, initiator, dquot)) { - sprintf(quotamessage, "%s: write failed, %s file limit reached\n", - dquot->dq_mnt->mnt_dirname, quotatypes[type]); - tty_write_message(tty, quotamessage); - dquot->dq_flags |= DQ_INODES; - } - return(NO_QUOTA); + !ignore_hardlimit(dquot)) { + print_warning(dquot, DQ_INODES, "%s: write failed, %s file limit reached\n", + dquot->dq_mnt->mnt_dirname, quotatypes[type]); + 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, initiator)) { - if (need_print_warning(type, initiator, dquot)) { - sprintf(quotamessage, "%s: warning, %s file quota exceeded too long.\n", - dquot->dq_mnt->mnt_dirname, quotatypes[type]); - tty_write_message(tty, quotamessage); - } - return(NO_QUOTA); + !ignore_hardlimit(dquot)) { + print_warning(dquot, DQ_INODES, "%s: warning, %s file quota exceeded too long.\n", + dquot->dq_mnt->mnt_dirname, quotatypes[type]); + return NO_QUOTA; } if (dquot->dq_isoftlimit && (dquot->dq_curinodes + inodes) > dquot->dq_isoftlimit && dquot->dq_itime == 0) { - if (need_print_warning(type, initiator, dquot)) { - sprintf(quotamessage, "%s: warning, %s file quota exceeded\n", - dquot->dq_mnt->mnt_dirname, quotatypes[type]); - tty_write_message(tty, quotamessage); - } + print_warning(dquot, 0, "%s: warning, %s file quota exceeded\n", + dquot->dq_mnt->mnt_dirname, quotatypes[type]); dquot->dq_itime = CURRENT_TIME + dquot->dq_mnt->mnt_dquot.inode_expire[type]; } - return(QUOTA_OK); + return QUOTA_OK; } -static int check_bdq(struct dquot *dquot, short type, u_long blocks, uid_t initiator, - struct tty_struct *tty, char warn) +static int check_bdq(struct dquot *dquot, u_long blocks, char prealloc) { + short type = dquot->dq_type; + if (blocks <= 0 || dquot->dq_flags & DQ_FAKE) - return(QUOTA_OK); + return QUOTA_OK; if (dquot->dq_bhardlimit && (dquot->dq_curblocks + blocks) > dquot->dq_bhardlimit && - !ignore_hardlimit(dquot, initiator)) { - if (warn && (dquot->dq_flags & DQ_BLKS) == 0 && - need_print_warning(type, initiator, dquot)) { - sprintf(quotamessage, "%s: write failed, %s disk limit reached.\n", - dquot->dq_mnt->mnt_dirname, quotatypes[type]); - tty_write_message(tty, quotamessage); - dquot->dq_flags |= DQ_BLKS; - } - return(NO_QUOTA); + !ignore_hardlimit(dquot)) { + if (!prealloc) + print_warning(dquot, DQ_BLKS, "%s: write failed, %s disk limit reached.\n", + dquot->dq_mnt->mnt_dirname, quotatypes[type]); + 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, initiator)) { - if (warn && need_print_warning(type, initiator, dquot)) { - sprintf(quotamessage, "%s: write failed, %s disk quota exceeded too long.\n", - dquot->dq_mnt->mnt_dirname, quotatypes[type]); - tty_write_message(tty, quotamessage); - } - return(NO_QUOTA); + !ignore_hardlimit(dquot)) { + if (!prealloc) + print_warning(dquot, DQ_BLKS, "%s: write failed, %s disk quota exceeded too long.\n", + dquot->dq_mnt->mnt_dirname, quotatypes[type]); + return NO_QUOTA; } if (dquot->dq_bsoftlimit && (dquot->dq_curblocks + blocks) > dquot->dq_bsoftlimit && dquot->dq_btime == 0) { - if (warn && need_print_warning(type, initiator, dquot)) { - sprintf(quotamessage, "%s: warning, %s disk quota exceeded\n", - dquot->dq_mnt->mnt_dirname, quotatypes[type]); - tty_write_message(tty, quotamessage); + if (!prealloc) { + print_warning(dquot, 0, "%s: warning, %s disk quota exceeded\n", + dquot->dq_mnt->mnt_dirname, quotatypes[type]); + dquot->dq_btime = CURRENT_TIME + dquot->dq_mnt->mnt_dquot.block_expire[type]; } - dquot->dq_btime = CURRENT_TIME + dquot->dq_mnt->mnt_dquot.block_expire[type]; + else + /* + * We don't allow preallocation to exceed softlimit so exceeding will + * be always printed + */ + return NO_QUOTA; } - return(QUOTA_OK); + return QUOTA_OK; } /* @@ -874,9 +970,11 @@ static int get_quota(kdev_t dev, int id, short type, struct dqblk *dqblk) if (dquot == NODQUOT) goto out; + lock_dquot(dquot); /* We must protect against invalidating the quota */ error = -EFAULT; if (dqblk && !copy_to_user(dqblk, &dquot->dq_dqb, sizeof(struct dqblk))) error = 0; + unlock_dquot(dquot); dqput(dquot); out: return error; @@ -917,14 +1015,14 @@ static int quota_root_squash(kdev_t dev, short type, int *addr) * 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(size_t isize, size_t blksize) +static u_long isize_to_blocks(loff_t isize, size_t blksize_bits) { u_long blocks; u_long indirect; - if (!blksize) - blksize = BLOCK_SIZE; - blocks = (isize / blksize) + ((isize % blksize) ? 1 : 0); + 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)) { @@ -934,7 +1032,7 @@ static u_long isize_to_blocks(size_t isize, size_t blksize) } blocks += indirect; } - return(blocks); + return blocks; } /* @@ -951,6 +1049,9 @@ void dquot_initialize(struct inode *inode, short type) if (S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode) || S_ISLNK(inode->i_mode)) { + /* We don't want to have quotas on quota files - nasty deadlocks possible */ + if (is_quotafile(inode)) + return; for (cnt = 0; cnt < MAXQUOTAS; cnt++) { if (type != -1 && cnt != type) continue; @@ -968,6 +1069,8 @@ void dquot_initialize(struct inode *inode, short type) break; } dquot = dqget(inode->i_dev, id, cnt); + if (dquot == NODQUOT) + continue; if (inode->i_dquot[cnt] != NODQUOT) { dqput(dquot); continue; @@ -1002,50 +1105,73 @@ void dquot_drop(struct inode *inode) /* * Note: this is a blocking operation. */ -int dquot_alloc_block(const struct inode *inode, unsigned long number, uid_t initiator, - char warn) +int dquot_alloc_block(const struct inode *inode, unsigned long number, char warn) { - unsigned short cnt; - struct tty_struct *tty = current->tty; + int cnt; + struct dquot *dquot[MAXQUOTAS]; for (cnt = 0; cnt < MAXQUOTAS; cnt++) { - if (inode->i_dquot[cnt] == NODQUOT) + dquot[cnt] = dqduplicate(inode->i_dquot[cnt]); + if (dquot[cnt] == NODQUOT) continue; - if (check_bdq(inode->i_dquot[cnt], cnt, number, initiator, tty, warn)) - return(NO_QUOTA); + lock_dquot(dquot[cnt]); + if (check_bdq(dquot[cnt], number, warn)) + goto put_all; } for (cnt = 0; cnt < MAXQUOTAS; cnt++) { - if (inode->i_dquot[cnt] == NODQUOT) + if (dquot[cnt] == NODQUOT) continue; - dquot_incr_blocks(inode->i_dquot[cnt], number); + dquot_incr_blocks(dquot[cnt], number); + unlock_dquot(dquot[cnt]); + dqput(dquot[cnt]); } - return(QUOTA_OK); + return QUOTA_OK; +put_all: + for (; cnt >= 0; cnt--) { + if (dquot[cnt] == NODQUOT) + continue; + unlock_dquot(dquot[cnt]); + dqput(dquot[cnt]); + } + return NO_QUOTA; } /* * Note: this is a blocking operation. */ -int dquot_alloc_inode(const struct inode *inode, unsigned long number, uid_t initiator) +int dquot_alloc_inode(const struct inode *inode, unsigned long number) { - unsigned short cnt; - struct tty_struct *tty = current->tty; + int cnt; + struct dquot *dquot[MAXQUOTAS]; for (cnt = 0; cnt < MAXQUOTAS; cnt++) { - if (inode->i_dquot[cnt] == NODQUOT) + dquot[cnt] = dqduplicate(inode -> i_dquot[cnt]); + if (dquot[cnt] == NODQUOT) continue; - if (check_idq(inode->i_dquot[cnt], cnt, number, initiator, tty)) - return(NO_QUOTA); + lock_dquot(dquot[cnt]); + if (check_idq(dquot[cnt], number)) + goto put_all; } for (cnt = 0; cnt < MAXQUOTAS; cnt++) { - if (inode->i_dquot[cnt] == NODQUOT) + if (dquot[cnt] == NODQUOT) continue; - dquot_incr_inodes(inode->i_dquot[cnt], number); + dquot_incr_inodes(dquot[cnt], number); + unlock_dquot(dquot[cnt]); + dqput(dquot[cnt]); } - return(QUOTA_OK); + return QUOTA_OK; +put_all: + for (; cnt >= 0; cnt--) { + if (dquot[cnt] == NODQUOT) + continue; + unlock_dquot(dquot[cnt]); + dqput(dquot[cnt]); + } + return NO_QUOTA; } /* @@ -1054,11 +1180,14 @@ int dquot_alloc_inode(const struct inode *inode, unsigned long number, uid_t ini void dquot_free_block(const struct inode *inode, unsigned long number) { unsigned short cnt; + struct dquot *dquot; for (cnt = 0; cnt < MAXQUOTAS; cnt++) { - if (inode->i_dquot[cnt] == NODQUOT) + dquot = inode->i_dquot[cnt]; + if (dquot == NODQUOT) continue; - dquot_decr_blocks(inode->i_dquot[cnt], number); + wait_on_dquot(dquot); + dquot_decr_blocks(dquot, number); } } @@ -1068,11 +1197,14 @@ void dquot_free_block(const struct inode *inode, unsigned long number) void dquot_free_inode(const struct inode *inode, unsigned long number) { unsigned short cnt; + struct dquot *dquot; for (cnt = 0; cnt < MAXQUOTAS; cnt++) { - if (inode->i_dquot[cnt] == NODQUOT) + dquot = inode->i_dquot[cnt]; + if (dquot == NODQUOT) continue; - dquot_decr_inodes(inode->i_dquot[cnt], number); + wait_on_dquot(dquot); + dquot_decr_inodes(dquot, number); } } @@ -1081,21 +1213,24 @@ void dquot_free_inode(const struct inode *inode, unsigned long number) * * Note: this is a blocking operation. */ -int dquot_transfer(struct inode *inode, struct iattr *iattr, char direction, uid_t initiator) +int dquot_transfer(struct dentry *dentry, struct iattr *iattr) { + struct inode *inode = dentry -> d_inode; unsigned long blocks; struct dquot *transfer_from[MAXQUOTAS]; struct dquot *transfer_to[MAXQUOTAS]; - struct tty_struct *tty = current->tty; short cnt, disc; + int error = -EDQUOT; + if (!inode) + return -ENOENT; /* * Find out if this filesystem uses i_blocks. */ - if (inode->i_blksize == 0) - blocks = isize_to_blocks(inode->i_size, BLOCK_SIZE); + if (!inode->i_sb || !inode->i_sb->s_blocksize) + blocks = isize_to_blocks(inode->i_size, BLOCK_SIZE_BITS); else - blocks = (inode->i_blocks / 2); + blocks = (inode->i_blocks >> 1); /* * Build the transfer_from and transfer_to lists and check quotas to see @@ -1112,27 +1247,60 @@ int dquot_transfer(struct inode *inode, struct iattr *iattr, char direction, uid case USRQUOTA: if (inode->i_uid == iattr->ia_uid) continue; - transfer_from[cnt] = dqget(inode->i_dev, (direction) ? iattr->ia_uid : inode->i_uid, cnt); - transfer_to[cnt] = dqget(inode->i_dev, (direction) ? inode->i_uid : iattr->ia_uid, cnt); + /* We can get transfer_from from inode, can't we? */ + transfer_from[cnt] = dqget(inode->i_dev, inode->i_uid, cnt); + transfer_to[cnt] = dqget(inode->i_dev, iattr->ia_uid, cnt); break; case GRPQUOTA: if (inode->i_gid == iattr->ia_gid) continue; - transfer_from[cnt] = dqget(inode->i_dev, (direction) ? iattr->ia_gid : inode->i_gid, cnt); - transfer_to[cnt] = dqget(inode->i_dev, (direction) ? inode->i_gid : iattr->ia_gid, cnt); + transfer_from[cnt] = dqget(inode->i_dev, inode->i_gid, cnt); + transfer_to[cnt] = dqget(inode->i_dev, iattr->ia_gid, cnt); break; } - if (check_idq(transfer_to[cnt], cnt, 1, initiator, tty) == NO_QUOTA || - check_bdq(transfer_to[cnt], cnt, blocks, initiator, tty, 0) == NO_QUOTA) { - for (disc = 0; disc <= cnt; disc++) { - dqput(transfer_from[disc]); - dqput(transfer_to[disc]); + /* Something bad (eg. quotaoff) happened while we were sleeping? */ + if (transfer_from[cnt] == NODQUOT || transfer_to[cnt] == NODQUOT) + { + if (transfer_from[cnt] != NODQUOT) { + dqput(transfer_from[cnt]); + transfer_from[cnt] = NODQUOT; + } + if (transfer_to[cnt] != NODQUOT) { + dqput(transfer_to[cnt]); + transfer_to[cnt] = NODQUOT; } - return(NO_QUOTA); + continue; + } + /* + * We have to lock the quotas to prevent races... + */ + if (transfer_from[cnt] < transfer_to[cnt]) + { + lock_dquot(transfer_from[cnt]); + lock_dquot(transfer_to[cnt]); + } + else + { + lock_dquot(transfer_to[cnt]); + lock_dquot(transfer_from[cnt]); + } + + /* + * The entries might got invalidated while locking. The second + * dqget() could block and so the first structure might got + * invalidated or locked... + */ + if (!transfer_to[cnt]->dq_mnt || !transfer_from[cnt]->dq_mnt || + check_idq(transfer_to[cnt], 1) == NO_QUOTA || + check_bdq(transfer_to[cnt], blocks, 0) == NO_QUOTA) { + cnt++; + goto put_all; } } + if ((error = notify_change(dentry, iattr))) + goto put_all; /* * Finally perform the needed transfer from transfer_from to transfer_to, * and release any pointers to dquots not needed anymore. @@ -1144,28 +1312,39 @@ int dquot_transfer(struct inode *inode, struct iattr *iattr, char direction, uid if (transfer_from[cnt] == NODQUOT && transfer_to[cnt] == NODQUOT) continue; - if (transfer_from[cnt] != NODQUOT) { - dquot_decr_inodes(transfer_from[cnt], 1); - dquot_decr_blocks(transfer_from[cnt], blocks); - } + dquot_decr_inodes(transfer_from[cnt], 1); + dquot_decr_blocks(transfer_from[cnt], blocks); - if (transfer_to[cnt] != NODQUOT) { - dquot_incr_inodes(transfer_to[cnt], 1); - dquot_incr_blocks(transfer_to[cnt], blocks); - } + dquot_incr_inodes(transfer_to[cnt], 1); + dquot_incr_blocks(transfer_to[cnt], blocks); + unlock_dquot(transfer_from[cnt]); + dqput(transfer_from[cnt]); if (inode->i_dquot[cnt] != NODQUOT) { struct dquot *temp = inode->i_dquot[cnt]; inode->i_dquot[cnt] = transfer_to[cnt]; + unlock_dquot(transfer_to[cnt]); dqput(temp); - dqput(transfer_from[cnt]); } else { - dqput(transfer_from[cnt]); + unlock_dquot(transfer_to[cnt]); dqput(transfer_to[cnt]); } } - return(QUOTA_OK); + return 0; +put_all: + for (disc = 0; disc < cnt; disc++) { + /* There should be none or both pointers set but... */ + if (transfer_to[disc] != NODQUOT) { + unlock_dquot(transfer_to[disc]); + dqput(transfer_to[disc]); + } + if (transfer_from[disc] != NODQUOT) { + unlock_dquot(transfer_from[disc]); + dqput(transfer_from[disc]); + } + } + return error; } @@ -1221,6 +1400,9 @@ static inline void reset_enable_flags(struct vfsmount *vfsmnt, short type) } } +/* Function in inode.c - remove pointers to dquots in icache */ +extern void remove_dquot_ref(kdev_t, short); + /* * Turn quota off on a device. type == -1 ==> quotaoff for all types (umount) */ @@ -1229,48 +1411,57 @@ int quota_off(kdev_t dev, short type) struct vfsmount *vfsmnt; struct file *filp; short cnt; + int enabled = 0; + + /* We don't need to search for vfsmnt each time - umount has to wait for us */ + vfsmnt = lookup_vfsmnt(dev); + if (!vfsmnt || !vfsmnt->mnt_sb) + goto out; + /* We need to serialize quota_off() for device */ + down(&vfsmnt->mnt_dquot.dqoff_sem); for (cnt = 0; cnt < MAXQUOTAS; cnt++) { if (type != -1 && cnt != type) continue; - - vfsmnt = lookup_vfsmnt(dev); - if (!vfsmnt) - goto out; - if (!vfsmnt->mnt_sb) - goto out; if (!is_enabled(vfsmnt, cnt)) continue; reset_enable_flags(vfsmnt, cnt); /* Note: these are blocking operations */ - reset_dquot_ptrs(dev, cnt); + remove_dquot_ref(dev, cnt); invalidate_dquots(dev, cnt); + /* Wait for any pending IO - remove me as soon as invalidate is more polite */ + down(&vfsmnt->mnt_dquot.dqio_sem); filp = vfsmnt->mnt_dquot.files[cnt]; vfsmnt->mnt_dquot.files[cnt] = (struct file *)NULL; vfsmnt->mnt_dquot.inode_expire[cnt] = 0; vfsmnt->mnt_dquot.block_expire[cnt] = 0; + up(&vfsmnt->mnt_dquot.dqio_sem); fput(filp); - } + } /* * Check whether any quota is still enabled, * and if not clear the dq_op pointer. */ - vfsmnt = lookup_vfsmnt(dev); - if (vfsmnt && vfsmnt->mnt_sb) { - int enabled = 0; - for (cnt = 0; cnt < MAXQUOTAS; cnt++) - enabled |= is_enabled(vfsmnt, cnt); - if (!enabled) - vfsmnt->mnt_sb->dq_op = NULL; - } - + for (cnt = 0; cnt < MAXQUOTAS; cnt++) + enabled |= is_enabled(vfsmnt, cnt); + if (!enabled) + vfsmnt->mnt_sb->dq_op = NULL; + up(&vfsmnt->mnt_dquot.dqoff_sem); out: 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)); +} + int quota_on(kdev_t dev, short type, char *path) { struct file *f; @@ -1287,47 +1478,52 @@ int quota_on(kdev_t dev, short type, char *path) if (is_enabled(vfsmnt, type)) return -EBUSY; - mnt_dquot = &vfsmnt->mnt_dquot; + mnt_dquot = &vfsmnt->mnt_dquot; + down(&mnt_dquot->dqoff_sem); tmp = getname(path); error = PTR_ERR(tmp); if (IS_ERR(tmp)) - return error; + goto out_lock; f = filp_open(tmp, O_RDWR, 0600); putname(tmp); - if (IS_ERR(f)) - return PTR_ERR(f); - /* sanity checks */ + error = PTR_ERR(f); + if (IS_ERR(f)) + goto out_lock; error = -EIO; if (!f->f_op->read && !f->f_op->write) - goto cleanup; + goto out_f; inode = f->f_dentry->d_inode; error = -EACCES; if (!S_ISREG(inode->i_mode)) - goto cleanup; + goto out_f; error = -EINVAL; - if (inode->i_size == 0 || (inode->i_size % sizeof(struct dqblk)) != 0) - goto cleanup; + if (inode->i_size == 0 || !check_quotafile_size(inode->i_size)) + goto out_f; + dquot_drop(inode); /* We don't want quota on quota files */ - /* OK, there we go */ set_enable_flags(vfsmnt, type); mnt_dquot->files[type] = f; dquot = dqget(dev, 0, type); - mnt_dquot->inode_expire[type] = (dquot) ? dquot->dq_itime : MAX_IQ_TIME; - mnt_dquot->block_expire[type] = (dquot) ? dquot->dq_btime : MAX_DQ_TIME; + mnt_dquot->inode_expire[type] = (dquot != NODQUOT) ? dquot->dq_itime : MAX_IQ_TIME; + mnt_dquot->block_expire[type] = (dquot != NODQUOT) ? dquot->dq_btime : MAX_DQ_TIME; dqput(dquot); vfsmnt->mnt_sb->dq_op = &dquot_operations; add_dquot_ref(dev, type); - return(0); + up(&mnt_dquot->dqoff_sem); + return 0; -cleanup: - fput(f); - return error; +out_f: + filp_close(f, NULL); +out_lock: + up(&mnt_dquot->dqoff_sem); + + return error; } /* @@ -1348,6 +1544,9 @@ asmlinkage long sys_quotactl(int cmd, const char *special, int id, caddr_t addr) if ((u_int) type >= MAXQUOTAS) goto out; + if (id & ~0xFFFF) + goto out; + ret = -EPERM; switch (cmds) { case Q_SYNC: @@ -1355,7 +1554,7 @@ asmlinkage long sys_quotactl(int cmd, const char *special, int id, caddr_t addr) break; case Q_GETQUOTA: if (((type == USRQUOTA && current->euid != id) || - (type == GRPQUOTA && current->egid != id)) && + (type == GRPQUOTA && in_egroup_p(id))) && !capable(CAP_SYS_RESOURCE)) goto out; break; @@ -1365,7 +1564,7 @@ asmlinkage long sys_quotactl(int cmd, const char *special, int id, caddr_t addr) } ret = -EINVAL; - dev = 0; + dev = NODEV; if (special != NULL || (cmds != Q_SYNC && cmds != Q_GETSTATS)) { mode_t mode; struct dentry * dentry; |