quota: Move quota files into separate directory
Quota subsystem has more and more files. It's time to create a dir for it. Signed-off-by: Jan Kara <jack@suse.cz>
Cette révision appartient à :
59
fs/quota/Kconfig
Fichier normal
59
fs/quota/Kconfig
Fichier normal
@@ -0,0 +1,59 @@
|
||||
#
|
||||
# Quota configuration
|
||||
#
|
||||
|
||||
config QUOTA
|
||||
bool "Quota support"
|
||||
help
|
||||
If you say Y here, you will be able to set per user limits for disk
|
||||
usage (also called disk quotas). Currently, it works for the
|
||||
ext2, ext3, and reiserfs file system. ext3 also supports journalled
|
||||
quotas for which you don't need to run quotacheck(8) after an unclean
|
||||
shutdown.
|
||||
For further details, read the Quota mini-HOWTO, available from
|
||||
<http://www.tldp.org/docs.html#howto>, or the documentation provided
|
||||
with the quota tools. Probably the quota support is only useful for
|
||||
multi user systems. If unsure, say N.
|
||||
|
||||
config QUOTA_NETLINK_INTERFACE
|
||||
bool "Report quota messages through netlink interface"
|
||||
depends on QUOTA && NET
|
||||
help
|
||||
If you say Y here, quota warnings (about exceeding softlimit, reaching
|
||||
hardlimit, etc.) will be reported through netlink interface. If unsure,
|
||||
say Y.
|
||||
|
||||
config PRINT_QUOTA_WARNING
|
||||
bool "Print quota warnings to console (OBSOLETE)"
|
||||
depends on QUOTA
|
||||
default y
|
||||
help
|
||||
If you say Y here, quota warnings (about exceeding softlimit, reaching
|
||||
hardlimit, etc.) will be printed to the process' controlling terminal.
|
||||
Note that this behavior is currently deprecated and may go away in
|
||||
future. Please use notification via netlink socket instead.
|
||||
|
||||
# Generic support for tree structured quota files. Seleted when needed.
|
||||
config QUOTA_TREE
|
||||
tristate
|
||||
|
||||
config QFMT_V1
|
||||
tristate "Old quota format support"
|
||||
depends on QUOTA
|
||||
help
|
||||
This quota format was (is) used by kernels earlier than 2.4.22. If
|
||||
you have quota working and you don't want to convert to new quota
|
||||
format say Y here.
|
||||
|
||||
config QFMT_V2
|
||||
tristate "Quota format v2 support"
|
||||
depends on QUOTA
|
||||
select QUOTA_TREE
|
||||
help
|
||||
This quota format allows using quotas with 32-bit UIDs/GIDs. If you
|
||||
need this functionality say Y here.
|
||||
|
||||
config QUOTACTL
|
||||
bool
|
||||
depends on XFS_QUOTA || QUOTA
|
||||
default y
|
14
fs/quota/Makefile
Fichier normal
14
fs/quota/Makefile
Fichier normal
@@ -0,0 +1,14 @@
|
||||
#
|
||||
# Makefile for the Linux filesystems.
|
||||
#
|
||||
# 14 Sep 2000, Christoph Hellwig <hch@infradead.org>
|
||||
# Rewritten to use lists instead of if-statements.
|
||||
#
|
||||
|
||||
obj-y :=
|
||||
|
||||
obj-$(CONFIG_QUOTA) += dquot.o
|
||||
obj-$(CONFIG_QFMT_V1) += quota_v1.o
|
||||
obj-$(CONFIG_QFMT_V2) += quota_v2.o
|
||||
obj-$(CONFIG_QUOTA_TREE) += quota_tree.o
|
||||
obj-$(CONFIG_QUOTACTL) += quota.o
|
2564
fs/quota/dquot.c
Fichier normal
2564
fs/quota/dquot.c
Fichier normal
Fichier diff supprimé car celui-ci est trop grand
Voir la Diff
513
fs/quota/quota.c
Fichier normal
513
fs/quota/quota.c
Fichier normal
@@ -0,0 +1,513 @@
|
||||
/*
|
||||
* Quota code necessary even when VFS quota support is not compiled
|
||||
* into the kernel. The interesting stuff is over in dquot.c, here
|
||||
* we have symbols for initial quotactl(2) handling, the sysctl(2)
|
||||
* variables, etc - things needed even when quota support disabled.
|
||||
*/
|
||||
|
||||
#include <linux/fs.h>
|
||||
#include <linux/namei.h>
|
||||
#include <linux/slab.h>
|
||||
#include <asm/current.h>
|
||||
#include <asm/uaccess.h>
|
||||
#include <linux/compat.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/security.h>
|
||||
#include <linux/syscalls.h>
|
||||
#include <linux/buffer_head.h>
|
||||
#include <linux/capability.h>
|
||||
#include <linux/quotaops.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
/* Check validity of generic quotactl commands */
|
||||
static int generic_quotactl_valid(struct super_block *sb, int type, int cmd, qid_t id)
|
||||
{
|
||||
if (type >= MAXQUOTAS)
|
||||
return -EINVAL;
|
||||
if (!sb && cmd != Q_SYNC)
|
||||
return -ENODEV;
|
||||
/* Is operation supported? */
|
||||
if (sb && !sb->s_qcop)
|
||||
return -ENOSYS;
|
||||
|
||||
switch (cmd) {
|
||||
case Q_GETFMT:
|
||||
break;
|
||||
case Q_QUOTAON:
|
||||
if (!sb->s_qcop->quota_on)
|
||||
return -ENOSYS;
|
||||
break;
|
||||
case Q_QUOTAOFF:
|
||||
if (!sb->s_qcop->quota_off)
|
||||
return -ENOSYS;
|
||||
break;
|
||||
case Q_SETINFO:
|
||||
if (!sb->s_qcop->set_info)
|
||||
return -ENOSYS;
|
||||
break;
|
||||
case Q_GETINFO:
|
||||
if (!sb->s_qcop->get_info)
|
||||
return -ENOSYS;
|
||||
break;
|
||||
case Q_SETQUOTA:
|
||||
if (!sb->s_qcop->set_dqblk)
|
||||
return -ENOSYS;
|
||||
break;
|
||||
case Q_GETQUOTA:
|
||||
if (!sb->s_qcop->get_dqblk)
|
||||
return -ENOSYS;
|
||||
break;
|
||||
case Q_SYNC:
|
||||
if (sb && !sb->s_qcop->quota_sync)
|
||||
return -ENOSYS;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Is quota turned on for commands which need it? */
|
||||
switch (cmd) {
|
||||
case Q_GETFMT:
|
||||
case Q_GETINFO:
|
||||
case Q_SETINFO:
|
||||
case Q_SETQUOTA:
|
||||
case Q_GETQUOTA:
|
||||
/* This is just informative test so we are satisfied without a lock */
|
||||
if (!sb_has_quota_active(sb, type))
|
||||
return -ESRCH;
|
||||
}
|
||||
|
||||
/* Check privileges */
|
||||
if (cmd == Q_GETQUOTA) {
|
||||
if (((type == USRQUOTA && current_euid() != id) ||
|
||||
(type == GRPQUOTA && !in_egroup_p(id))) &&
|
||||
!capable(CAP_SYS_ADMIN))
|
||||
return -EPERM;
|
||||
}
|
||||
else if (cmd != Q_GETFMT && cmd != Q_SYNC && cmd != Q_GETINFO)
|
||||
if (!capable(CAP_SYS_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Check validity of XFS Quota Manager commands */
|
||||
static int xqm_quotactl_valid(struct super_block *sb, int type, int cmd, qid_t id)
|
||||
{
|
||||
if (type >= XQM_MAXQUOTAS)
|
||||
return -EINVAL;
|
||||
if (!sb)
|
||||
return -ENODEV;
|
||||
if (!sb->s_qcop)
|
||||
return -ENOSYS;
|
||||
|
||||
switch (cmd) {
|
||||
case Q_XQUOTAON:
|
||||
case Q_XQUOTAOFF:
|
||||
case Q_XQUOTARM:
|
||||
if (!sb->s_qcop->set_xstate)
|
||||
return -ENOSYS;
|
||||
break;
|
||||
case Q_XGETQSTAT:
|
||||
if (!sb->s_qcop->get_xstate)
|
||||
return -ENOSYS;
|
||||
break;
|
||||
case Q_XSETQLIM:
|
||||
if (!sb->s_qcop->set_xquota)
|
||||
return -ENOSYS;
|
||||
break;
|
||||
case Q_XGETQUOTA:
|
||||
if (!sb->s_qcop->get_xquota)
|
||||
return -ENOSYS;
|
||||
break;
|
||||
case Q_XQUOTASYNC:
|
||||
if (!sb->s_qcop->quota_sync)
|
||||
return -ENOSYS;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Check privileges */
|
||||
if (cmd == Q_XGETQUOTA) {
|
||||
if (((type == XQM_USRQUOTA && current_euid() != id) ||
|
||||
(type == XQM_GRPQUOTA && !in_egroup_p(id))) &&
|
||||
!capable(CAP_SYS_ADMIN))
|
||||
return -EPERM;
|
||||
} else if (cmd != Q_XGETQSTAT && cmd != Q_XQUOTASYNC) {
|
||||
if (!capable(CAP_SYS_ADMIN))
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int check_quotactl_valid(struct super_block *sb, int type, int cmd, qid_t id)
|
||||
{
|
||||
int error;
|
||||
|
||||
if (XQM_COMMAND(cmd))
|
||||
error = xqm_quotactl_valid(sb, type, cmd, id);
|
||||
else
|
||||
error = generic_quotactl_valid(sb, type, cmd, id);
|
||||
if (!error)
|
||||
error = security_quotactl(cmd, type, id, sb);
|
||||
return error;
|
||||
}
|
||||
|
||||
static void quota_sync_sb(struct super_block *sb, int type)
|
||||
{
|
||||
int cnt;
|
||||
|
||||
sb->s_qcop->quota_sync(sb, type);
|
||||
|
||||
if (sb_dqopt(sb)->flags & DQUOT_QUOTA_SYS_FILE)
|
||||
return;
|
||||
/* This is not very clever (and fast) but currently I don't know about
|
||||
* any other simple way of getting quota data to disk and we must get
|
||||
* them there for userspace to be visible... */
|
||||
if (sb->s_op->sync_fs)
|
||||
sb->s_op->sync_fs(sb, 1);
|
||||
sync_blockdev(sb->s_bdev);
|
||||
|
||||
/*
|
||||
* Now when everything is written we can discard the pagecache so
|
||||
* that userspace sees the changes.
|
||||
*/
|
||||
mutex_lock(&sb_dqopt(sb)->dqonoff_mutex);
|
||||
for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
|
||||
if (type != -1 && cnt != type)
|
||||
continue;
|
||||
if (!sb_has_quota_active(sb, cnt))
|
||||
continue;
|
||||
mutex_lock_nested(&sb_dqopt(sb)->files[cnt]->i_mutex, I_MUTEX_QUOTA);
|
||||
truncate_inode_pages(&sb_dqopt(sb)->files[cnt]->i_data, 0);
|
||||
mutex_unlock(&sb_dqopt(sb)->files[cnt]->i_mutex);
|
||||
}
|
||||
mutex_unlock(&sb_dqopt(sb)->dqonoff_mutex);
|
||||
}
|
||||
|
||||
void sync_dquots(struct super_block *sb, int type)
|
||||
{
|
||||
int cnt;
|
||||
|
||||
if (sb) {
|
||||
if (sb->s_qcop->quota_sync)
|
||||
quota_sync_sb(sb, type);
|
||||
return;
|
||||
}
|
||||
|
||||
spin_lock(&sb_lock);
|
||||
restart:
|
||||
list_for_each_entry(sb, &super_blocks, s_list) {
|
||||
/* This test just improves performance so it needn't be reliable... */
|
||||
for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
|
||||
if (type != -1 && type != cnt)
|
||||
continue;
|
||||
if (!sb_has_quota_active(sb, cnt))
|
||||
continue;
|
||||
if (!info_dirty(&sb_dqopt(sb)->info[cnt]) &&
|
||||
list_empty(&sb_dqopt(sb)->info[cnt].dqi_dirty_list))
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
if (cnt == MAXQUOTAS)
|
||||
continue;
|
||||
sb->s_count++;
|
||||
spin_unlock(&sb_lock);
|
||||
down_read(&sb->s_umount);
|
||||
if (sb->s_root && sb->s_qcop->quota_sync)
|
||||
quota_sync_sb(sb, type);
|
||||
up_read(&sb->s_umount);
|
||||
spin_lock(&sb_lock);
|
||||
if (__put_super_and_need_restart(sb))
|
||||
goto restart;
|
||||
}
|
||||
spin_unlock(&sb_lock);
|
||||
}
|
||||
|
||||
/* Copy parameters and call proper function */
|
||||
static int do_quotactl(struct super_block *sb, int type, int cmd, qid_t id, void __user *addr)
|
||||
{
|
||||
int ret;
|
||||
|
||||
switch (cmd) {
|
||||
case Q_QUOTAON: {
|
||||
char *pathname;
|
||||
|
||||
if (IS_ERR(pathname = getname(addr)))
|
||||
return PTR_ERR(pathname);
|
||||
ret = sb->s_qcop->quota_on(sb, type, id, pathname, 0);
|
||||
putname(pathname);
|
||||
return ret;
|
||||
}
|
||||
case Q_QUOTAOFF:
|
||||
return sb->s_qcop->quota_off(sb, type, 0);
|
||||
|
||||
case Q_GETFMT: {
|
||||
__u32 fmt;
|
||||
|
||||
down_read(&sb_dqopt(sb)->dqptr_sem);
|
||||
if (!sb_has_quota_active(sb, type)) {
|
||||
up_read(&sb_dqopt(sb)->dqptr_sem);
|
||||
return -ESRCH;
|
||||
}
|
||||
fmt = sb_dqopt(sb)->info[type].dqi_format->qf_fmt_id;
|
||||
up_read(&sb_dqopt(sb)->dqptr_sem);
|
||||
if (copy_to_user(addr, &fmt, sizeof(fmt)))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
case Q_GETINFO: {
|
||||
struct if_dqinfo info;
|
||||
|
||||
if ((ret = sb->s_qcop->get_info(sb, type, &info)))
|
||||
return ret;
|
||||
if (copy_to_user(addr, &info, sizeof(info)))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
case Q_SETINFO: {
|
||||
struct if_dqinfo info;
|
||||
|
||||
if (copy_from_user(&info, addr, sizeof(info)))
|
||||
return -EFAULT;
|
||||
return sb->s_qcop->set_info(sb, type, &info);
|
||||
}
|
||||
case Q_GETQUOTA: {
|
||||
struct if_dqblk idq;
|
||||
|
||||
if ((ret = sb->s_qcop->get_dqblk(sb, type, id, &idq)))
|
||||
return ret;
|
||||
if (copy_to_user(addr, &idq, sizeof(idq)))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
case Q_SETQUOTA: {
|
||||
struct if_dqblk idq;
|
||||
|
||||
if (copy_from_user(&idq, addr, sizeof(idq)))
|
||||
return -EFAULT;
|
||||
return sb->s_qcop->set_dqblk(sb, type, id, &idq);
|
||||
}
|
||||
case Q_SYNC:
|
||||
sync_dquots(sb, type);
|
||||
return 0;
|
||||
|
||||
case Q_XQUOTAON:
|
||||
case Q_XQUOTAOFF:
|
||||
case Q_XQUOTARM: {
|
||||
__u32 flags;
|
||||
|
||||
if (copy_from_user(&flags, addr, sizeof(flags)))
|
||||
return -EFAULT;
|
||||
return sb->s_qcop->set_xstate(sb, flags, cmd);
|
||||
}
|
||||
case Q_XGETQSTAT: {
|
||||
struct fs_quota_stat fqs;
|
||||
|
||||
if ((ret = sb->s_qcop->get_xstate(sb, &fqs)))
|
||||
return ret;
|
||||
if (copy_to_user(addr, &fqs, sizeof(fqs)))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
case Q_XSETQLIM: {
|
||||
struct fs_disk_quota fdq;
|
||||
|
||||
if (copy_from_user(&fdq, addr, sizeof(fdq)))
|
||||
return -EFAULT;
|
||||
return sb->s_qcop->set_xquota(sb, type, id, &fdq);
|
||||
}
|
||||
case Q_XGETQUOTA: {
|
||||
struct fs_disk_quota fdq;
|
||||
|
||||
if ((ret = sb->s_qcop->get_xquota(sb, type, id, &fdq)))
|
||||
return ret;
|
||||
if (copy_to_user(addr, &fdq, sizeof(fdq)))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
case Q_XQUOTASYNC:
|
||||
return sb->s_qcop->quota_sync(sb, type);
|
||||
/* We never reach here unless validity check is broken */
|
||||
default:
|
||||
BUG();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* look up a superblock on which quota ops will be performed
|
||||
* - use the name of a block device to find the superblock thereon
|
||||
*/
|
||||
static inline struct super_block *quotactl_block(const char __user *special)
|
||||
{
|
||||
#ifdef CONFIG_BLOCK
|
||||
struct block_device *bdev;
|
||||
struct super_block *sb;
|
||||
char *tmp = getname(special);
|
||||
|
||||
if (IS_ERR(tmp))
|
||||
return ERR_CAST(tmp);
|
||||
bdev = lookup_bdev(tmp);
|
||||
putname(tmp);
|
||||
if (IS_ERR(bdev))
|
||||
return ERR_CAST(bdev);
|
||||
sb = get_super(bdev);
|
||||
bdput(bdev);
|
||||
if (!sb)
|
||||
return ERR_PTR(-ENODEV);
|
||||
|
||||
return sb;
|
||||
#else
|
||||
return ERR_PTR(-ENODEV);
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
SYSCALL_DEFINE4(quotactl, unsigned int, cmd, const char __user *, special,
|
||||
qid_t, id, void __user *, addr)
|
||||
{
|
||||
uint cmds, type;
|
||||
struct super_block *sb = NULL;
|
||||
int ret;
|
||||
|
||||
cmds = cmd >> SUBCMDSHIFT;
|
||||
type = cmd & SUBCMDMASK;
|
||||
|
||||
if (cmds != Q_SYNC || special) {
|
||||
sb = quotactl_block(special);
|
||||
if (IS_ERR(sb))
|
||||
return PTR_ERR(sb);
|
||||
}
|
||||
|
||||
ret = check_quotactl_valid(sb, type, cmds, id);
|
||||
if (ret >= 0)
|
||||
ret = do_quotactl(sb, type, cmds, id, addr);
|
||||
if (sb)
|
||||
drop_super(sb);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_COMPAT_FOR_U64_ALIGNMENT)
|
||||
/*
|
||||
* This code works only for 32 bit quota tools over 64 bit OS (x86_64, ia64)
|
||||
* and is necessary due to alignment problems.
|
||||
*/
|
||||
struct compat_if_dqblk {
|
||||
compat_u64 dqb_bhardlimit;
|
||||
compat_u64 dqb_bsoftlimit;
|
||||
compat_u64 dqb_curspace;
|
||||
compat_u64 dqb_ihardlimit;
|
||||
compat_u64 dqb_isoftlimit;
|
||||
compat_u64 dqb_curinodes;
|
||||
compat_u64 dqb_btime;
|
||||
compat_u64 dqb_itime;
|
||||
compat_uint_t dqb_valid;
|
||||
};
|
||||
|
||||
/* XFS structures */
|
||||
struct compat_fs_qfilestat {
|
||||
compat_u64 dqb_bhardlimit;
|
||||
compat_u64 qfs_nblks;
|
||||
compat_uint_t qfs_nextents;
|
||||
};
|
||||
|
||||
struct compat_fs_quota_stat {
|
||||
__s8 qs_version;
|
||||
__u16 qs_flags;
|
||||
__s8 qs_pad;
|
||||
struct compat_fs_qfilestat qs_uquota;
|
||||
struct compat_fs_qfilestat qs_gquota;
|
||||
compat_uint_t qs_incoredqs;
|
||||
compat_int_t qs_btimelimit;
|
||||
compat_int_t qs_itimelimit;
|
||||
compat_int_t qs_rtbtimelimit;
|
||||
__u16 qs_bwarnlimit;
|
||||
__u16 qs_iwarnlimit;
|
||||
};
|
||||
|
||||
asmlinkage long sys32_quotactl(unsigned int cmd, const char __user *special,
|
||||
qid_t id, void __user *addr)
|
||||
{
|
||||
unsigned int cmds;
|
||||
struct if_dqblk __user *dqblk;
|
||||
struct compat_if_dqblk __user *compat_dqblk;
|
||||
struct fs_quota_stat __user *fsqstat;
|
||||
struct compat_fs_quota_stat __user *compat_fsqstat;
|
||||
compat_uint_t data;
|
||||
u16 xdata;
|
||||
long ret;
|
||||
|
||||
cmds = cmd >> SUBCMDSHIFT;
|
||||
|
||||
switch (cmds) {
|
||||
case Q_GETQUOTA:
|
||||
dqblk = compat_alloc_user_space(sizeof(struct if_dqblk));
|
||||
compat_dqblk = addr;
|
||||
ret = sys_quotactl(cmd, special, id, dqblk);
|
||||
if (ret)
|
||||
break;
|
||||
if (copy_in_user(compat_dqblk, dqblk, sizeof(*compat_dqblk)) ||
|
||||
get_user(data, &dqblk->dqb_valid) ||
|
||||
put_user(data, &compat_dqblk->dqb_valid))
|
||||
ret = -EFAULT;
|
||||
break;
|
||||
case Q_SETQUOTA:
|
||||
dqblk = compat_alloc_user_space(sizeof(struct if_dqblk));
|
||||
compat_dqblk = addr;
|
||||
ret = -EFAULT;
|
||||
if (copy_in_user(dqblk, compat_dqblk, sizeof(*compat_dqblk)) ||
|
||||
get_user(data, &compat_dqblk->dqb_valid) ||
|
||||
put_user(data, &dqblk->dqb_valid))
|
||||
break;
|
||||
ret = sys_quotactl(cmd, special, id, dqblk);
|
||||
break;
|
||||
case Q_XGETQSTAT:
|
||||
fsqstat = compat_alloc_user_space(sizeof(struct fs_quota_stat));
|
||||
compat_fsqstat = addr;
|
||||
ret = sys_quotactl(cmd, special, id, fsqstat);
|
||||
if (ret)
|
||||
break;
|
||||
ret = -EFAULT;
|
||||
/* Copying qs_version, qs_flags, qs_pad */
|
||||
if (copy_in_user(compat_fsqstat, fsqstat,
|
||||
offsetof(struct compat_fs_quota_stat, qs_uquota)))
|
||||
break;
|
||||
/* Copying qs_uquota */
|
||||
if (copy_in_user(&compat_fsqstat->qs_uquota,
|
||||
&fsqstat->qs_uquota,
|
||||
sizeof(compat_fsqstat->qs_uquota)) ||
|
||||
get_user(data, &fsqstat->qs_uquota.qfs_nextents) ||
|
||||
put_user(data, &compat_fsqstat->qs_uquota.qfs_nextents))
|
||||
break;
|
||||
/* Copying qs_gquota */
|
||||
if (copy_in_user(&compat_fsqstat->qs_gquota,
|
||||
&fsqstat->qs_gquota,
|
||||
sizeof(compat_fsqstat->qs_gquota)) ||
|
||||
get_user(data, &fsqstat->qs_gquota.qfs_nextents) ||
|
||||
put_user(data, &compat_fsqstat->qs_gquota.qfs_nextents))
|
||||
break;
|
||||
/* Copying the rest */
|
||||
if (copy_in_user(&compat_fsqstat->qs_incoredqs,
|
||||
&fsqstat->qs_incoredqs,
|
||||
sizeof(struct compat_fs_quota_stat) -
|
||||
offsetof(struct compat_fs_quota_stat, qs_incoredqs)) ||
|
||||
get_user(xdata, &fsqstat->qs_iwarnlimit) ||
|
||||
put_user(xdata, &compat_fsqstat->qs_iwarnlimit))
|
||||
break;
|
||||
ret = 0;
|
||||
break;
|
||||
default:
|
||||
ret = sys_quotactl(cmd, special, id, addr);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
#endif
|
645
fs/quota/quota_tree.c
Fichier normal
645
fs/quota/quota_tree.c
Fichier normal
@@ -0,0 +1,645 @@
|
||||
/*
|
||||
* vfsv0 quota IO operations on file
|
||||
*/
|
||||
|
||||
#include <linux/errno.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/mount.h>
|
||||
#include <linux/dqblk_v2.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/quotaops.h>
|
||||
|
||||
#include <asm/byteorder.h>
|
||||
|
||||
#include "quota_tree.h"
|
||||
|
||||
MODULE_AUTHOR("Jan Kara");
|
||||
MODULE_DESCRIPTION("Quota trie support");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
#define __QUOTA_QT_PARANOIA
|
||||
|
||||
typedef char *dqbuf_t;
|
||||
|
||||
static int get_index(struct qtree_mem_dqinfo *info, qid_t id, int depth)
|
||||
{
|
||||
unsigned int epb = info->dqi_usable_bs >> 2;
|
||||
|
||||
depth = info->dqi_qtree_depth - depth - 1;
|
||||
while (depth--)
|
||||
id /= epb;
|
||||
return id % epb;
|
||||
}
|
||||
|
||||
/* Number of entries in one blocks */
|
||||
static inline int qtree_dqstr_in_blk(struct qtree_mem_dqinfo *info)
|
||||
{
|
||||
return (info->dqi_usable_bs - sizeof(struct qt_disk_dqdbheader))
|
||||
/ info->dqi_entry_size;
|
||||
}
|
||||
|
||||
static dqbuf_t getdqbuf(size_t size)
|
||||
{
|
||||
dqbuf_t buf = kmalloc(size, GFP_NOFS);
|
||||
if (!buf)
|
||||
printk(KERN_WARNING "VFS: Not enough memory for quota buffers.\n");
|
||||
return buf;
|
||||
}
|
||||
|
||||
static inline void freedqbuf(dqbuf_t buf)
|
||||
{
|
||||
kfree(buf);
|
||||
}
|
||||
|
||||
static inline ssize_t read_blk(struct qtree_mem_dqinfo *info, uint blk, dqbuf_t buf)
|
||||
{
|
||||
struct super_block *sb = info->dqi_sb;
|
||||
|
||||
memset(buf, 0, info->dqi_usable_bs);
|
||||
return sb->s_op->quota_read(sb, info->dqi_type, (char *)buf,
|
||||
info->dqi_usable_bs, blk << info->dqi_blocksize_bits);
|
||||
}
|
||||
|
||||
static inline ssize_t write_blk(struct qtree_mem_dqinfo *info, uint blk, dqbuf_t buf)
|
||||
{
|
||||
struct super_block *sb = info->dqi_sb;
|
||||
|
||||
return sb->s_op->quota_write(sb, info->dqi_type, (char *)buf,
|
||||
info->dqi_usable_bs, blk << info->dqi_blocksize_bits);
|
||||
}
|
||||
|
||||
/* Remove empty block from list and return it */
|
||||
static int get_free_dqblk(struct qtree_mem_dqinfo *info)
|
||||
{
|
||||
dqbuf_t buf = getdqbuf(info->dqi_usable_bs);
|
||||
struct qt_disk_dqdbheader *dh = (struct qt_disk_dqdbheader *)buf;
|
||||
int ret, blk;
|
||||
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
if (info->dqi_free_blk) {
|
||||
blk = info->dqi_free_blk;
|
||||
ret = read_blk(info, blk, buf);
|
||||
if (ret < 0)
|
||||
goto out_buf;
|
||||
info->dqi_free_blk = le32_to_cpu(dh->dqdh_next_free);
|
||||
}
|
||||
else {
|
||||
memset(buf, 0, info->dqi_usable_bs);
|
||||
/* Assure block allocation... */
|
||||
ret = write_blk(info, info->dqi_blocks, buf);
|
||||
if (ret < 0)
|
||||
goto out_buf;
|
||||
blk = info->dqi_blocks++;
|
||||
}
|
||||
mark_info_dirty(info->dqi_sb, info->dqi_type);
|
||||
ret = blk;
|
||||
out_buf:
|
||||
freedqbuf(buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Insert empty block to the list */
|
||||
static int put_free_dqblk(struct qtree_mem_dqinfo *info, dqbuf_t buf, uint blk)
|
||||
{
|
||||
struct qt_disk_dqdbheader *dh = (struct qt_disk_dqdbheader *)buf;
|
||||
int err;
|
||||
|
||||
dh->dqdh_next_free = cpu_to_le32(info->dqi_free_blk);
|
||||
dh->dqdh_prev_free = cpu_to_le32(0);
|
||||
dh->dqdh_entries = cpu_to_le16(0);
|
||||
err = write_blk(info, blk, buf);
|
||||
if (err < 0)
|
||||
return err;
|
||||
info->dqi_free_blk = blk;
|
||||
mark_info_dirty(info->dqi_sb, info->dqi_type);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Remove given block from the list of blocks with free entries */
|
||||
static int remove_free_dqentry(struct qtree_mem_dqinfo *info, dqbuf_t buf, uint blk)
|
||||
{
|
||||
dqbuf_t tmpbuf = getdqbuf(info->dqi_usable_bs);
|
||||
struct qt_disk_dqdbheader *dh = (struct qt_disk_dqdbheader *)buf;
|
||||
uint nextblk = le32_to_cpu(dh->dqdh_next_free);
|
||||
uint prevblk = le32_to_cpu(dh->dqdh_prev_free);
|
||||
int err;
|
||||
|
||||
if (!tmpbuf)
|
||||
return -ENOMEM;
|
||||
if (nextblk) {
|
||||
err = read_blk(info, nextblk, tmpbuf);
|
||||
if (err < 0)
|
||||
goto out_buf;
|
||||
((struct qt_disk_dqdbheader *)tmpbuf)->dqdh_prev_free =
|
||||
dh->dqdh_prev_free;
|
||||
err = write_blk(info, nextblk, tmpbuf);
|
||||
if (err < 0)
|
||||
goto out_buf;
|
||||
}
|
||||
if (prevblk) {
|
||||
err = read_blk(info, prevblk, tmpbuf);
|
||||
if (err < 0)
|
||||
goto out_buf;
|
||||
((struct qt_disk_dqdbheader *)tmpbuf)->dqdh_next_free =
|
||||
dh->dqdh_next_free;
|
||||
err = write_blk(info, prevblk, tmpbuf);
|
||||
if (err < 0)
|
||||
goto out_buf;
|
||||
} else {
|
||||
info->dqi_free_entry = nextblk;
|
||||
mark_info_dirty(info->dqi_sb, info->dqi_type);
|
||||
}
|
||||
freedqbuf(tmpbuf);
|
||||
dh->dqdh_next_free = dh->dqdh_prev_free = cpu_to_le32(0);
|
||||
/* No matter whether write succeeds block is out of list */
|
||||
if (write_blk(info, blk, buf) < 0)
|
||||
printk(KERN_ERR "VFS: Can't write block (%u) with free entries.\n", blk);
|
||||
return 0;
|
||||
out_buf:
|
||||
freedqbuf(tmpbuf);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Insert given block to the beginning of list with free entries */
|
||||
static int insert_free_dqentry(struct qtree_mem_dqinfo *info, dqbuf_t buf, uint blk)
|
||||
{
|
||||
dqbuf_t tmpbuf = getdqbuf(info->dqi_usable_bs);
|
||||
struct qt_disk_dqdbheader *dh = (struct qt_disk_dqdbheader *)buf;
|
||||
int err;
|
||||
|
||||
if (!tmpbuf)
|
||||
return -ENOMEM;
|
||||
dh->dqdh_next_free = cpu_to_le32(info->dqi_free_entry);
|
||||
dh->dqdh_prev_free = cpu_to_le32(0);
|
||||
err = write_blk(info, blk, buf);
|
||||
if (err < 0)
|
||||
goto out_buf;
|
||||
if (info->dqi_free_entry) {
|
||||
err = read_blk(info, info->dqi_free_entry, tmpbuf);
|
||||
if (err < 0)
|
||||
goto out_buf;
|
||||
((struct qt_disk_dqdbheader *)tmpbuf)->dqdh_prev_free =
|
||||
cpu_to_le32(blk);
|
||||
err = write_blk(info, info->dqi_free_entry, tmpbuf);
|
||||
if (err < 0)
|
||||
goto out_buf;
|
||||
}
|
||||
freedqbuf(tmpbuf);
|
||||
info->dqi_free_entry = blk;
|
||||
mark_info_dirty(info->dqi_sb, info->dqi_type);
|
||||
return 0;
|
||||
out_buf:
|
||||
freedqbuf(tmpbuf);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Is the entry in the block free? */
|
||||
int qtree_entry_unused(struct qtree_mem_dqinfo *info, char *disk)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < info->dqi_entry_size; i++)
|
||||
if (disk[i])
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
EXPORT_SYMBOL(qtree_entry_unused);
|
||||
|
||||
/* Find space for dquot */
|
||||
static uint find_free_dqentry(struct qtree_mem_dqinfo *info,
|
||||
struct dquot *dquot, int *err)
|
||||
{
|
||||
uint blk, i;
|
||||
struct qt_disk_dqdbheader *dh;
|
||||
dqbuf_t buf = getdqbuf(info->dqi_usable_bs);
|
||||
char *ddquot;
|
||||
|
||||
*err = 0;
|
||||
if (!buf) {
|
||||
*err = -ENOMEM;
|
||||
return 0;
|
||||
}
|
||||
dh = (struct qt_disk_dqdbheader *)buf;
|
||||
if (info->dqi_free_entry) {
|
||||
blk = info->dqi_free_entry;
|
||||
*err = read_blk(info, blk, buf);
|
||||
if (*err < 0)
|
||||
goto out_buf;
|
||||
} else {
|
||||
blk = get_free_dqblk(info);
|
||||
if ((int)blk < 0) {
|
||||
*err = blk;
|
||||
freedqbuf(buf);
|
||||
return 0;
|
||||
}
|
||||
memset(buf, 0, info->dqi_usable_bs);
|
||||
/* This is enough as block is already zeroed and entry list is empty... */
|
||||
info->dqi_free_entry = blk;
|
||||
mark_info_dirty(dquot->dq_sb, dquot->dq_type);
|
||||
}
|
||||
/* Block will be full? */
|
||||
if (le16_to_cpu(dh->dqdh_entries) + 1 >= qtree_dqstr_in_blk(info)) {
|
||||
*err = remove_free_dqentry(info, buf, blk);
|
||||
if (*err < 0) {
|
||||
printk(KERN_ERR "VFS: find_free_dqentry(): Can't "
|
||||
"remove block (%u) from entry free list.\n",
|
||||
blk);
|
||||
goto out_buf;
|
||||
}
|
||||
}
|
||||
le16_add_cpu(&dh->dqdh_entries, 1);
|
||||
/* Find free structure in block */
|
||||
for (i = 0, ddquot = ((char *)buf) + sizeof(struct qt_disk_dqdbheader);
|
||||
i < qtree_dqstr_in_blk(info) && !qtree_entry_unused(info, ddquot);
|
||||
i++, ddquot += info->dqi_entry_size);
|
||||
#ifdef __QUOTA_QT_PARANOIA
|
||||
if (i == qtree_dqstr_in_blk(info)) {
|
||||
printk(KERN_ERR "VFS: find_free_dqentry(): Data block full "
|
||||
"but it shouldn't.\n");
|
||||
*err = -EIO;
|
||||
goto out_buf;
|
||||
}
|
||||
#endif
|
||||
*err = write_blk(info, blk, buf);
|
||||
if (*err < 0) {
|
||||
printk(KERN_ERR "VFS: find_free_dqentry(): Can't write quota "
|
||||
"data block %u.\n", blk);
|
||||
goto out_buf;
|
||||
}
|
||||
dquot->dq_off = (blk << info->dqi_blocksize_bits) +
|
||||
sizeof(struct qt_disk_dqdbheader) +
|
||||
i * info->dqi_entry_size;
|
||||
freedqbuf(buf);
|
||||
return blk;
|
||||
out_buf:
|
||||
freedqbuf(buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Insert reference to structure into the trie */
|
||||
static int do_insert_tree(struct qtree_mem_dqinfo *info, struct dquot *dquot,
|
||||
uint *treeblk, int depth)
|
||||
{
|
||||
dqbuf_t buf = getdqbuf(info->dqi_usable_bs);
|
||||
int ret = 0, newson = 0, newact = 0;
|
||||
__le32 *ref;
|
||||
uint newblk;
|
||||
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
if (!*treeblk) {
|
||||
ret = get_free_dqblk(info);
|
||||
if (ret < 0)
|
||||
goto out_buf;
|
||||
*treeblk = ret;
|
||||
memset(buf, 0, info->dqi_usable_bs);
|
||||
newact = 1;
|
||||
} else {
|
||||
ret = read_blk(info, *treeblk, buf);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "VFS: Can't read tree quota block "
|
||||
"%u.\n", *treeblk);
|
||||
goto out_buf;
|
||||
}
|
||||
}
|
||||
ref = (__le32 *)buf;
|
||||
newblk = le32_to_cpu(ref[get_index(info, dquot->dq_id, depth)]);
|
||||
if (!newblk)
|
||||
newson = 1;
|
||||
if (depth == info->dqi_qtree_depth - 1) {
|
||||
#ifdef __QUOTA_QT_PARANOIA
|
||||
if (newblk) {
|
||||
printk(KERN_ERR "VFS: Inserting already present quota "
|
||||
"entry (block %u).\n",
|
||||
le32_to_cpu(ref[get_index(info,
|
||||
dquot->dq_id, depth)]));
|
||||
ret = -EIO;
|
||||
goto out_buf;
|
||||
}
|
||||
#endif
|
||||
newblk = find_free_dqentry(info, dquot, &ret);
|
||||
} else {
|
||||
ret = do_insert_tree(info, dquot, &newblk, depth+1);
|
||||
}
|
||||
if (newson && ret >= 0) {
|
||||
ref[get_index(info, dquot->dq_id, depth)] =
|
||||
cpu_to_le32(newblk);
|
||||
ret = write_blk(info, *treeblk, buf);
|
||||
} else if (newact && ret < 0) {
|
||||
put_free_dqblk(info, buf, *treeblk);
|
||||
}
|
||||
out_buf:
|
||||
freedqbuf(buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Wrapper for inserting quota structure into tree */
|
||||
static inline int dq_insert_tree(struct qtree_mem_dqinfo *info,
|
||||
struct dquot *dquot)
|
||||
{
|
||||
int tmp = QT_TREEOFF;
|
||||
return do_insert_tree(info, dquot, &tmp, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* We don't have to be afraid of deadlocks as we never have quotas on quota files...
|
||||
*/
|
||||
int qtree_write_dquot(struct qtree_mem_dqinfo *info, struct dquot *dquot)
|
||||
{
|
||||
int type = dquot->dq_type;
|
||||
struct super_block *sb = dquot->dq_sb;
|
||||
ssize_t ret;
|
||||
dqbuf_t ddquot = getdqbuf(info->dqi_entry_size);
|
||||
|
||||
if (!ddquot)
|
||||
return -ENOMEM;
|
||||
|
||||
/* dq_off is guarded by dqio_mutex */
|
||||
if (!dquot->dq_off) {
|
||||
ret = dq_insert_tree(info, dquot);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "VFS: Error %zd occurred while "
|
||||
"creating quota.\n", ret);
|
||||
freedqbuf(ddquot);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
spin_lock(&dq_data_lock);
|
||||
info->dqi_ops->mem2disk_dqblk(ddquot, dquot);
|
||||
spin_unlock(&dq_data_lock);
|
||||
ret = sb->s_op->quota_write(sb, type, (char *)ddquot,
|
||||
info->dqi_entry_size, dquot->dq_off);
|
||||
if (ret != info->dqi_entry_size) {
|
||||
printk(KERN_WARNING "VFS: dquota write failed on dev %s\n",
|
||||
sb->s_id);
|
||||
if (ret >= 0)
|
||||
ret = -ENOSPC;
|
||||
} else {
|
||||
ret = 0;
|
||||
}
|
||||
dqstats.writes++;
|
||||
freedqbuf(ddquot);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(qtree_write_dquot);
|
||||
|
||||
/* Free dquot entry in data block */
|
||||
static int free_dqentry(struct qtree_mem_dqinfo *info, struct dquot *dquot,
|
||||
uint blk)
|
||||
{
|
||||
struct qt_disk_dqdbheader *dh;
|
||||
dqbuf_t buf = getdqbuf(info->dqi_usable_bs);
|
||||
int ret = 0;
|
||||
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
if (dquot->dq_off >> info->dqi_blocksize_bits != blk) {
|
||||
printk(KERN_ERR "VFS: Quota structure has offset to other "
|
||||
"block (%u) than it should (%u).\n", blk,
|
||||
(uint)(dquot->dq_off >> info->dqi_blocksize_bits));
|
||||
goto out_buf;
|
||||
}
|
||||
ret = read_blk(info, blk, buf);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "VFS: Can't read quota data block %u\n", blk);
|
||||
goto out_buf;
|
||||
}
|
||||
dh = (struct qt_disk_dqdbheader *)buf;
|
||||
le16_add_cpu(&dh->dqdh_entries, -1);
|
||||
if (!le16_to_cpu(dh->dqdh_entries)) { /* Block got free? */
|
||||
ret = remove_free_dqentry(info, buf, blk);
|
||||
if (ret >= 0)
|
||||
ret = put_free_dqblk(info, buf, blk);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "VFS: Can't move quota data block (%u) "
|
||||
"to free list.\n", blk);
|
||||
goto out_buf;
|
||||
}
|
||||
} else {
|
||||
memset(buf +
|
||||
(dquot->dq_off & ((1 << info->dqi_blocksize_bits) - 1)),
|
||||
0, info->dqi_entry_size);
|
||||
if (le16_to_cpu(dh->dqdh_entries) ==
|
||||
qtree_dqstr_in_blk(info) - 1) {
|
||||
/* Insert will write block itself */
|
||||
ret = insert_free_dqentry(info, buf, blk);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "VFS: Can't insert quota data "
|
||||
"block (%u) to free entry list.\n", blk);
|
||||
goto out_buf;
|
||||
}
|
||||
} else {
|
||||
ret = write_blk(info, blk, buf);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "VFS: Can't write quota data "
|
||||
"block %u\n", blk);
|
||||
goto out_buf;
|
||||
}
|
||||
}
|
||||
}
|
||||
dquot->dq_off = 0; /* Quota is now unattached */
|
||||
out_buf:
|
||||
freedqbuf(buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Remove reference to dquot from tree */
|
||||
static int remove_tree(struct qtree_mem_dqinfo *info, struct dquot *dquot,
|
||||
uint *blk, int depth)
|
||||
{
|
||||
dqbuf_t buf = getdqbuf(info->dqi_usable_bs);
|
||||
int ret = 0;
|
||||
uint newblk;
|
||||
__le32 *ref = (__le32 *)buf;
|
||||
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
ret = read_blk(info, *blk, buf);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "VFS: Can't read quota data block %u\n", *blk);
|
||||
goto out_buf;
|
||||
}
|
||||
newblk = le32_to_cpu(ref[get_index(info, dquot->dq_id, depth)]);
|
||||
if (depth == info->dqi_qtree_depth - 1) {
|
||||
ret = free_dqentry(info, dquot, newblk);
|
||||
newblk = 0;
|
||||
} else {
|
||||
ret = remove_tree(info, dquot, &newblk, depth+1);
|
||||
}
|
||||
if (ret >= 0 && !newblk) {
|
||||
int i;
|
||||
ref[get_index(info, dquot->dq_id, depth)] = cpu_to_le32(0);
|
||||
/* Block got empty? */
|
||||
for (i = 0;
|
||||
i < (info->dqi_usable_bs >> 2) && !ref[i];
|
||||
i++);
|
||||
/* Don't put the root block into the free block list */
|
||||
if (i == (info->dqi_usable_bs >> 2)
|
||||
&& *blk != QT_TREEOFF) {
|
||||
put_free_dqblk(info, buf, *blk);
|
||||
*blk = 0;
|
||||
} else {
|
||||
ret = write_blk(info, *blk, buf);
|
||||
if (ret < 0)
|
||||
printk(KERN_ERR "VFS: Can't write quota tree "
|
||||
"block %u.\n", *blk);
|
||||
}
|
||||
}
|
||||
out_buf:
|
||||
freedqbuf(buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Delete dquot from tree */
|
||||
int qtree_delete_dquot(struct qtree_mem_dqinfo *info, struct dquot *dquot)
|
||||
{
|
||||
uint tmp = QT_TREEOFF;
|
||||
|
||||
if (!dquot->dq_off) /* Even not allocated? */
|
||||
return 0;
|
||||
return remove_tree(info, dquot, &tmp, 0);
|
||||
}
|
||||
EXPORT_SYMBOL(qtree_delete_dquot);
|
||||
|
||||
/* Find entry in block */
|
||||
static loff_t find_block_dqentry(struct qtree_mem_dqinfo *info,
|
||||
struct dquot *dquot, uint blk)
|
||||
{
|
||||
dqbuf_t buf = getdqbuf(info->dqi_usable_bs);
|
||||
loff_t ret = 0;
|
||||
int i;
|
||||
char *ddquot;
|
||||
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
ret = read_blk(info, blk, buf);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "VFS: Can't read quota tree block %u.\n", blk);
|
||||
goto out_buf;
|
||||
}
|
||||
for (i = 0, ddquot = ((char *)buf) + sizeof(struct qt_disk_dqdbheader);
|
||||
i < qtree_dqstr_in_blk(info) && !info->dqi_ops->is_id(ddquot, dquot);
|
||||
i++, ddquot += info->dqi_entry_size);
|
||||
if (i == qtree_dqstr_in_blk(info)) {
|
||||
printk(KERN_ERR "VFS: Quota for id %u referenced "
|
||||
"but not present.\n", dquot->dq_id);
|
||||
ret = -EIO;
|
||||
goto out_buf;
|
||||
} else {
|
||||
ret = (blk << info->dqi_blocksize_bits) + sizeof(struct
|
||||
qt_disk_dqdbheader) + i * info->dqi_entry_size;
|
||||
}
|
||||
out_buf:
|
||||
freedqbuf(buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Find entry for given id in the tree */
|
||||
static loff_t find_tree_dqentry(struct qtree_mem_dqinfo *info,
|
||||
struct dquot *dquot, uint blk, int depth)
|
||||
{
|
||||
dqbuf_t buf = getdqbuf(info->dqi_usable_bs);
|
||||
loff_t ret = 0;
|
||||
__le32 *ref = (__le32 *)buf;
|
||||
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
ret = read_blk(info, blk, buf);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "VFS: Can't read quota tree block %u.\n", blk);
|
||||
goto out_buf;
|
||||
}
|
||||
ret = 0;
|
||||
blk = le32_to_cpu(ref[get_index(info, dquot->dq_id, depth)]);
|
||||
if (!blk) /* No reference? */
|
||||
goto out_buf;
|
||||
if (depth < info->dqi_qtree_depth - 1)
|
||||
ret = find_tree_dqentry(info, dquot, blk, depth+1);
|
||||
else
|
||||
ret = find_block_dqentry(info, dquot, blk);
|
||||
out_buf:
|
||||
freedqbuf(buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Find entry for given id in the tree - wrapper function */
|
||||
static inline loff_t find_dqentry(struct qtree_mem_dqinfo *info,
|
||||
struct dquot *dquot)
|
||||
{
|
||||
return find_tree_dqentry(info, dquot, QT_TREEOFF, 0);
|
||||
}
|
||||
|
||||
int qtree_read_dquot(struct qtree_mem_dqinfo *info, struct dquot *dquot)
|
||||
{
|
||||
int type = dquot->dq_type;
|
||||
struct super_block *sb = dquot->dq_sb;
|
||||
loff_t offset;
|
||||
dqbuf_t ddquot;
|
||||
int ret = 0;
|
||||
|
||||
#ifdef __QUOTA_QT_PARANOIA
|
||||
/* Invalidated quota? */
|
||||
if (!sb_dqopt(dquot->dq_sb)->files[type]) {
|
||||
printk(KERN_ERR "VFS: Quota invalidated while reading!\n");
|
||||
return -EIO;
|
||||
}
|
||||
#endif
|
||||
/* Do we know offset of the dquot entry in the quota file? */
|
||||
if (!dquot->dq_off) {
|
||||
offset = find_dqentry(info, dquot);
|
||||
if (offset <= 0) { /* Entry not present? */
|
||||
if (offset < 0)
|
||||
printk(KERN_ERR "VFS: Can't read quota "
|
||||
"structure for id %u.\n", dquot->dq_id);
|
||||
dquot->dq_off = 0;
|
||||
set_bit(DQ_FAKE_B, &dquot->dq_flags);
|
||||
memset(&dquot->dq_dqb, 0, sizeof(struct mem_dqblk));
|
||||
ret = offset;
|
||||
goto out;
|
||||
}
|
||||
dquot->dq_off = offset;
|
||||
}
|
||||
ddquot = getdqbuf(info->dqi_entry_size);
|
||||
if (!ddquot)
|
||||
return -ENOMEM;
|
||||
ret = sb->s_op->quota_read(sb, type, (char *)ddquot,
|
||||
info->dqi_entry_size, dquot->dq_off);
|
||||
if (ret != info->dqi_entry_size) {
|
||||
if (ret >= 0)
|
||||
ret = -EIO;
|
||||
printk(KERN_ERR "VFS: Error while reading quota "
|
||||
"structure for id %u.\n", dquot->dq_id);
|
||||
set_bit(DQ_FAKE_B, &dquot->dq_flags);
|
||||
memset(&dquot->dq_dqb, 0, sizeof(struct mem_dqblk));
|
||||
freedqbuf(ddquot);
|
||||
goto out;
|
||||
}
|
||||
spin_lock(&dq_data_lock);
|
||||
info->dqi_ops->disk2mem_dqblk(dquot, ddquot);
|
||||
if (!dquot->dq_dqb.dqb_bhardlimit &&
|
||||
!dquot->dq_dqb.dqb_bsoftlimit &&
|
||||
!dquot->dq_dqb.dqb_ihardlimit &&
|
||||
!dquot->dq_dqb.dqb_isoftlimit)
|
||||
set_bit(DQ_FAKE_B, &dquot->dq_flags);
|
||||
spin_unlock(&dq_data_lock);
|
||||
freedqbuf(ddquot);
|
||||
out:
|
||||
dqstats.reads++;
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(qtree_read_dquot);
|
||||
|
||||
/* Check whether dquot should not be deleted. We know we are
|
||||
* the only one operating on dquot (thanks to dq_lock) */
|
||||
int qtree_release_dquot(struct qtree_mem_dqinfo *info, struct dquot *dquot)
|
||||
{
|
||||
if (test_bit(DQ_FAKE_B, &dquot->dq_flags) && !(dquot->dq_dqb.dqb_curinodes | dquot->dq_dqb.dqb_curspace))
|
||||
return qtree_delete_dquot(info, dquot);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(qtree_release_dquot);
|
25
fs/quota/quota_tree.h
Fichier normal
25
fs/quota/quota_tree.h
Fichier normal
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Definitions of structures for vfsv0 quota format
|
||||
*/
|
||||
|
||||
#ifndef _LINUX_QUOTA_TREE_H
|
||||
#define _LINUX_QUOTA_TREE_H
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/quota.h>
|
||||
|
||||
/*
|
||||
* Structure of header of block with quota structures. It is padded to 16 bytes so
|
||||
* there will be space for exactly 21 quota-entries in a block
|
||||
*/
|
||||
struct qt_disk_dqdbheader {
|
||||
__le32 dqdh_next_free; /* Number of next block with free entry */
|
||||
__le32 dqdh_prev_free; /* Number of previous block with free entry */
|
||||
__le16 dqdh_entries; /* Number of valid entries in block */
|
||||
__le16 dqdh_pad1;
|
||||
__le32 dqdh_pad2;
|
||||
};
|
||||
|
||||
#define QT_TREEOFF 1 /* Offset of tree in file in blocks */
|
||||
|
||||
#endif /* _LINUX_QUOTAIO_TREE_H */
|
218
fs/quota/quota_v1.c
Fichier normal
218
fs/quota/quota_v1.c
Fichier normal
@@ -0,0 +1,218 @@
|
||||
#include <linux/errno.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/quota.h>
|
||||
#include <linux/quotaops.h>
|
||||
#include <linux/dqblk_v1.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
#include <asm/byteorder.h>
|
||||
|
||||
#include "quotaio_v1.h"
|
||||
|
||||
MODULE_AUTHOR("Jan Kara");
|
||||
MODULE_DESCRIPTION("Old quota format support");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
#define QUOTABLOCK_BITS 10
|
||||
#define QUOTABLOCK_SIZE (1 << QUOTABLOCK_BITS)
|
||||
|
||||
static inline qsize_t v1_stoqb(qsize_t space)
|
||||
{
|
||||
return (space + QUOTABLOCK_SIZE - 1) >> QUOTABLOCK_BITS;
|
||||
}
|
||||
|
||||
static inline qsize_t v1_qbtos(qsize_t blocks)
|
||||
{
|
||||
return blocks << QUOTABLOCK_BITS;
|
||||
}
|
||||
|
||||
static void v1_disk2mem_dqblk(struct mem_dqblk *m, struct v1_disk_dqblk *d)
|
||||
{
|
||||
m->dqb_ihardlimit = d->dqb_ihardlimit;
|
||||
m->dqb_isoftlimit = d->dqb_isoftlimit;
|
||||
m->dqb_curinodes = d->dqb_curinodes;
|
||||
m->dqb_bhardlimit = v1_qbtos(d->dqb_bhardlimit);
|
||||
m->dqb_bsoftlimit = v1_qbtos(d->dqb_bsoftlimit);
|
||||
m->dqb_curspace = v1_qbtos(d->dqb_curblocks);
|
||||
m->dqb_itime = d->dqb_itime;
|
||||
m->dqb_btime = d->dqb_btime;
|
||||
}
|
||||
|
||||
static void v1_mem2disk_dqblk(struct v1_disk_dqblk *d, struct mem_dqblk *m)
|
||||
{
|
||||
d->dqb_ihardlimit = m->dqb_ihardlimit;
|
||||
d->dqb_isoftlimit = m->dqb_isoftlimit;
|
||||
d->dqb_curinodes = m->dqb_curinodes;
|
||||
d->dqb_bhardlimit = v1_stoqb(m->dqb_bhardlimit);
|
||||
d->dqb_bsoftlimit = v1_stoqb(m->dqb_bsoftlimit);
|
||||
d->dqb_curblocks = v1_stoqb(m->dqb_curspace);
|
||||
d->dqb_itime = m->dqb_itime;
|
||||
d->dqb_btime = m->dqb_btime;
|
||||
}
|
||||
|
||||
static int v1_read_dqblk(struct dquot *dquot)
|
||||
{
|
||||
int type = dquot->dq_type;
|
||||
struct v1_disk_dqblk dqblk;
|
||||
|
||||
if (!sb_dqopt(dquot->dq_sb)->files[type])
|
||||
return -EINVAL;
|
||||
|
||||
/* Set structure to 0s in case read fails/is after end of file */
|
||||
memset(&dqblk, 0, sizeof(struct v1_disk_dqblk));
|
||||
dquot->dq_sb->s_op->quota_read(dquot->dq_sb, type, (char *)&dqblk, sizeof(struct v1_disk_dqblk), v1_dqoff(dquot->dq_id));
|
||||
|
||||
v1_disk2mem_dqblk(&dquot->dq_dqb, &dqblk);
|
||||
if (dquot->dq_dqb.dqb_bhardlimit == 0 && dquot->dq_dqb.dqb_bsoftlimit == 0 &&
|
||||
dquot->dq_dqb.dqb_ihardlimit == 0 && dquot->dq_dqb.dqb_isoftlimit == 0)
|
||||
set_bit(DQ_FAKE_B, &dquot->dq_flags);
|
||||
dqstats.reads++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int v1_commit_dqblk(struct dquot *dquot)
|
||||
{
|
||||
short type = dquot->dq_type;
|
||||
ssize_t ret;
|
||||
struct v1_disk_dqblk dqblk;
|
||||
|
||||
v1_mem2disk_dqblk(&dqblk, &dquot->dq_dqb);
|
||||
if (dquot->dq_id == 0) {
|
||||
dqblk.dqb_btime = sb_dqopt(dquot->dq_sb)->info[type].dqi_bgrace;
|
||||
dqblk.dqb_itime = sb_dqopt(dquot->dq_sb)->info[type].dqi_igrace;
|
||||
}
|
||||
ret = 0;
|
||||
if (sb_dqopt(dquot->dq_sb)->files[type])
|
||||
ret = dquot->dq_sb->s_op->quota_write(dquot->dq_sb, type, (char *)&dqblk,
|
||||
sizeof(struct v1_disk_dqblk), v1_dqoff(dquot->dq_id));
|
||||
if (ret != sizeof(struct v1_disk_dqblk)) {
|
||||
printk(KERN_WARNING "VFS: dquota write failed on dev %s\n",
|
||||
dquot->dq_sb->s_id);
|
||||
if (ret >= 0)
|
||||
ret = -EIO;
|
||||
goto out;
|
||||
}
|
||||
ret = 0;
|
||||
|
||||
out:
|
||||
dqstats.writes++;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Magics of new quota format */
|
||||
#define V2_INITQMAGICS {\
|
||||
0xd9c01f11, /* USRQUOTA */\
|
||||
0xd9c01927 /* GRPQUOTA */\
|
||||
}
|
||||
|
||||
/* Header of new quota format */
|
||||
struct v2_disk_dqheader {
|
||||
__le32 dqh_magic; /* Magic number identifying file */
|
||||
__le32 dqh_version; /* File version */
|
||||
};
|
||||
|
||||
static int v1_check_quota_file(struct super_block *sb, int type)
|
||||
{
|
||||
struct inode *inode = sb_dqopt(sb)->files[type];
|
||||
ulong blocks;
|
||||
size_t off;
|
||||
struct v2_disk_dqheader dqhead;
|
||||
ssize_t size;
|
||||
loff_t isize;
|
||||
static const uint quota_magics[] = V2_INITQMAGICS;
|
||||
|
||||
isize = i_size_read(inode);
|
||||
if (!isize)
|
||||
return 0;
|
||||
blocks = isize >> BLOCK_SIZE_BITS;
|
||||
off = isize & (BLOCK_SIZE - 1);
|
||||
if ((blocks % sizeof(struct v1_disk_dqblk) * BLOCK_SIZE + off) % sizeof(struct v1_disk_dqblk))
|
||||
return 0;
|
||||
/* Doublecheck whether we didn't get file with new format - with old quotactl() this could happen */
|
||||
size = sb->s_op->quota_read(sb, type, (char *)&dqhead, sizeof(struct v2_disk_dqheader), 0);
|
||||
if (size != sizeof(struct v2_disk_dqheader))
|
||||
return 1; /* Probably not new format */
|
||||
if (le32_to_cpu(dqhead.dqh_magic) != quota_magics[type])
|
||||
return 1; /* Definitely not new format */
|
||||
printk(KERN_INFO "VFS: %s: Refusing to turn on old quota format on given file. It probably contains newer quota format.\n", sb->s_id);
|
||||
return 0; /* Seems like a new format file -> refuse it */
|
||||
}
|
||||
|
||||
static int v1_read_file_info(struct super_block *sb, int type)
|
||||
{
|
||||
struct quota_info *dqopt = sb_dqopt(sb);
|
||||
struct v1_disk_dqblk dqblk;
|
||||
int ret;
|
||||
|
||||
if ((ret = sb->s_op->quota_read(sb, type, (char *)&dqblk, sizeof(struct v1_disk_dqblk), v1_dqoff(0))) != sizeof(struct v1_disk_dqblk)) {
|
||||
if (ret >= 0)
|
||||
ret = -EIO;
|
||||
goto out;
|
||||
}
|
||||
ret = 0;
|
||||
/* limits are stored as unsigned 32-bit data */
|
||||
dqopt->info[type].dqi_maxblimit = 0xffffffff;
|
||||
dqopt->info[type].dqi_maxilimit = 0xffffffff;
|
||||
dqopt->info[type].dqi_igrace = dqblk.dqb_itime ? dqblk.dqb_itime : MAX_IQ_TIME;
|
||||
dqopt->info[type].dqi_bgrace = dqblk.dqb_btime ? dqblk.dqb_btime : MAX_DQ_TIME;
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int v1_write_file_info(struct super_block *sb, int type)
|
||||
{
|
||||
struct quota_info *dqopt = sb_dqopt(sb);
|
||||
struct v1_disk_dqblk dqblk;
|
||||
int ret;
|
||||
|
||||
dqopt->info[type].dqi_flags &= ~DQF_INFO_DIRTY;
|
||||
if ((ret = sb->s_op->quota_read(sb, type, (char *)&dqblk,
|
||||
sizeof(struct v1_disk_dqblk), v1_dqoff(0))) != sizeof(struct v1_disk_dqblk)) {
|
||||
if (ret >= 0)
|
||||
ret = -EIO;
|
||||
goto out;
|
||||
}
|
||||
dqblk.dqb_itime = dqopt->info[type].dqi_igrace;
|
||||
dqblk.dqb_btime = dqopt->info[type].dqi_bgrace;
|
||||
ret = sb->s_op->quota_write(sb, type, (char *)&dqblk,
|
||||
sizeof(struct v1_disk_dqblk), v1_dqoff(0));
|
||||
if (ret == sizeof(struct v1_disk_dqblk))
|
||||
ret = 0;
|
||||
else if (ret > 0)
|
||||
ret = -EIO;
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct quota_format_ops v1_format_ops = {
|
||||
.check_quota_file = v1_check_quota_file,
|
||||
.read_file_info = v1_read_file_info,
|
||||
.write_file_info = v1_write_file_info,
|
||||
.free_file_info = NULL,
|
||||
.read_dqblk = v1_read_dqblk,
|
||||
.commit_dqblk = v1_commit_dqblk,
|
||||
};
|
||||
|
||||
static struct quota_format_type v1_quota_format = {
|
||||
.qf_fmt_id = QFMT_VFS_OLD,
|
||||
.qf_ops = &v1_format_ops,
|
||||
.qf_owner = THIS_MODULE
|
||||
};
|
||||
|
||||
static int __init init_v1_quota_format(void)
|
||||
{
|
||||
return register_quota_format(&v1_quota_format);
|
||||
}
|
||||
|
||||
static void __exit exit_v1_quota_format(void)
|
||||
{
|
||||
unregister_quota_format(&v1_quota_format);
|
||||
}
|
||||
|
||||
module_init(init_v1_quota_format);
|
||||
module_exit(exit_v1_quota_format);
|
||||
|
236
fs/quota/quota_v2.c
Fichier normal
236
fs/quota/quota_v2.c
Fichier normal
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
* vfsv0 quota IO operations on file
|
||||
*/
|
||||
|
||||
#include <linux/errno.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/mount.h>
|
||||
#include <linux/dqblk_v2.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/quotaops.h>
|
||||
|
||||
#include <asm/byteorder.h>
|
||||
|
||||
#include "quota_tree.h"
|
||||
#include "quotaio_v2.h"
|
||||
|
||||
MODULE_AUTHOR("Jan Kara");
|
||||
MODULE_DESCRIPTION("Quota format v2 support");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
#define __QUOTA_V2_PARANOIA
|
||||
|
||||
static void v2_mem2diskdqb(void *dp, struct dquot *dquot);
|
||||
static void v2_disk2memdqb(struct dquot *dquot, void *dp);
|
||||
static int v2_is_id(void *dp, struct dquot *dquot);
|
||||
|
||||
static struct qtree_fmt_operations v2_qtree_ops = {
|
||||
.mem2disk_dqblk = v2_mem2diskdqb,
|
||||
.disk2mem_dqblk = v2_disk2memdqb,
|
||||
.is_id = v2_is_id,
|
||||
};
|
||||
|
||||
#define QUOTABLOCK_BITS 10
|
||||
#define QUOTABLOCK_SIZE (1 << QUOTABLOCK_BITS)
|
||||
|
||||
static inline qsize_t v2_stoqb(qsize_t space)
|
||||
{
|
||||
return (space + QUOTABLOCK_SIZE - 1) >> QUOTABLOCK_BITS;
|
||||
}
|
||||
|
||||
static inline qsize_t v2_qbtos(qsize_t blocks)
|
||||
{
|
||||
return blocks << QUOTABLOCK_BITS;
|
||||
}
|
||||
|
||||
/* Check whether given file is really vfsv0 quotafile */
|
||||
static int v2_check_quota_file(struct super_block *sb, int type)
|
||||
{
|
||||
struct v2_disk_dqheader dqhead;
|
||||
ssize_t size;
|
||||
static const uint quota_magics[] = V2_INITQMAGICS;
|
||||
static const uint quota_versions[] = V2_INITQVERSIONS;
|
||||
|
||||
size = sb->s_op->quota_read(sb, type, (char *)&dqhead, sizeof(struct v2_disk_dqheader), 0);
|
||||
if (size != sizeof(struct v2_disk_dqheader)) {
|
||||
printk("quota_v2: failed read expected=%zd got=%zd\n",
|
||||
sizeof(struct v2_disk_dqheader), size);
|
||||
return 0;
|
||||
}
|
||||
if (le32_to_cpu(dqhead.dqh_magic) != quota_magics[type] ||
|
||||
le32_to_cpu(dqhead.dqh_version) != quota_versions[type])
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Read information header from quota file */
|
||||
static int v2_read_file_info(struct super_block *sb, int type)
|
||||
{
|
||||
struct v2_disk_dqinfo dinfo;
|
||||
struct mem_dqinfo *info = sb_dqinfo(sb, type);
|
||||
struct qtree_mem_dqinfo *qinfo;
|
||||
ssize_t size;
|
||||
|
||||
size = sb->s_op->quota_read(sb, type, (char *)&dinfo,
|
||||
sizeof(struct v2_disk_dqinfo), V2_DQINFOOFF);
|
||||
if (size != sizeof(struct v2_disk_dqinfo)) {
|
||||
printk(KERN_WARNING "Can't read info structure on device %s.\n",
|
||||
sb->s_id);
|
||||
return -1;
|
||||
}
|
||||
info->dqi_priv = kmalloc(sizeof(struct qtree_mem_dqinfo), GFP_NOFS);
|
||||
if (!info->dqi_priv) {
|
||||
printk(KERN_WARNING
|
||||
"Not enough memory for quota information structure.\n");
|
||||
return -1;
|
||||
}
|
||||
qinfo = info->dqi_priv;
|
||||
/* limits are stored as unsigned 32-bit data */
|
||||
info->dqi_maxblimit = 0xffffffff;
|
||||
info->dqi_maxilimit = 0xffffffff;
|
||||
info->dqi_bgrace = le32_to_cpu(dinfo.dqi_bgrace);
|
||||
info->dqi_igrace = le32_to_cpu(dinfo.dqi_igrace);
|
||||
info->dqi_flags = le32_to_cpu(dinfo.dqi_flags);
|
||||
qinfo->dqi_sb = sb;
|
||||
qinfo->dqi_type = type;
|
||||
qinfo->dqi_blocks = le32_to_cpu(dinfo.dqi_blocks);
|
||||
qinfo->dqi_free_blk = le32_to_cpu(dinfo.dqi_free_blk);
|
||||
qinfo->dqi_free_entry = le32_to_cpu(dinfo.dqi_free_entry);
|
||||
qinfo->dqi_blocksize_bits = V2_DQBLKSIZE_BITS;
|
||||
qinfo->dqi_usable_bs = 1 << V2_DQBLKSIZE_BITS;
|
||||
qinfo->dqi_qtree_depth = qtree_depth(qinfo);
|
||||
qinfo->dqi_entry_size = sizeof(struct v2_disk_dqblk);
|
||||
qinfo->dqi_ops = &v2_qtree_ops;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Write information header to quota file */
|
||||
static int v2_write_file_info(struct super_block *sb, int type)
|
||||
{
|
||||
struct v2_disk_dqinfo dinfo;
|
||||
struct mem_dqinfo *info = sb_dqinfo(sb, type);
|
||||
struct qtree_mem_dqinfo *qinfo = info->dqi_priv;
|
||||
ssize_t size;
|
||||
|
||||
spin_lock(&dq_data_lock);
|
||||
info->dqi_flags &= ~DQF_INFO_DIRTY;
|
||||
dinfo.dqi_bgrace = cpu_to_le32(info->dqi_bgrace);
|
||||
dinfo.dqi_igrace = cpu_to_le32(info->dqi_igrace);
|
||||
dinfo.dqi_flags = cpu_to_le32(info->dqi_flags & DQF_MASK);
|
||||
spin_unlock(&dq_data_lock);
|
||||
dinfo.dqi_blocks = cpu_to_le32(qinfo->dqi_blocks);
|
||||
dinfo.dqi_free_blk = cpu_to_le32(qinfo->dqi_free_blk);
|
||||
dinfo.dqi_free_entry = cpu_to_le32(qinfo->dqi_free_entry);
|
||||
size = sb->s_op->quota_write(sb, type, (char *)&dinfo,
|
||||
sizeof(struct v2_disk_dqinfo), V2_DQINFOOFF);
|
||||
if (size != sizeof(struct v2_disk_dqinfo)) {
|
||||
printk(KERN_WARNING "Can't write info structure on device %s.\n",
|
||||
sb->s_id);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void v2_disk2memdqb(struct dquot *dquot, void *dp)
|
||||
{
|
||||
struct v2_disk_dqblk *d = dp, empty;
|
||||
struct mem_dqblk *m = &dquot->dq_dqb;
|
||||
|
||||
m->dqb_ihardlimit = le32_to_cpu(d->dqb_ihardlimit);
|
||||
m->dqb_isoftlimit = le32_to_cpu(d->dqb_isoftlimit);
|
||||
m->dqb_curinodes = le32_to_cpu(d->dqb_curinodes);
|
||||
m->dqb_itime = le64_to_cpu(d->dqb_itime);
|
||||
m->dqb_bhardlimit = v2_qbtos(le32_to_cpu(d->dqb_bhardlimit));
|
||||
m->dqb_bsoftlimit = v2_qbtos(le32_to_cpu(d->dqb_bsoftlimit));
|
||||
m->dqb_curspace = le64_to_cpu(d->dqb_curspace);
|
||||
m->dqb_btime = le64_to_cpu(d->dqb_btime);
|
||||
/* We need to escape back all-zero structure */
|
||||
memset(&empty, 0, sizeof(struct v2_disk_dqblk));
|
||||
empty.dqb_itime = cpu_to_le64(1);
|
||||
if (!memcmp(&empty, dp, sizeof(struct v2_disk_dqblk)))
|
||||
m->dqb_itime = 0;
|
||||
}
|
||||
|
||||
static void v2_mem2diskdqb(void *dp, struct dquot *dquot)
|
||||
{
|
||||
struct v2_disk_dqblk *d = dp;
|
||||
struct mem_dqblk *m = &dquot->dq_dqb;
|
||||
struct qtree_mem_dqinfo *info =
|
||||
sb_dqinfo(dquot->dq_sb, dquot->dq_type)->dqi_priv;
|
||||
|
||||
d->dqb_ihardlimit = cpu_to_le32(m->dqb_ihardlimit);
|
||||
d->dqb_isoftlimit = cpu_to_le32(m->dqb_isoftlimit);
|
||||
d->dqb_curinodes = cpu_to_le32(m->dqb_curinodes);
|
||||
d->dqb_itime = cpu_to_le64(m->dqb_itime);
|
||||
d->dqb_bhardlimit = cpu_to_le32(v2_stoqb(m->dqb_bhardlimit));
|
||||
d->dqb_bsoftlimit = cpu_to_le32(v2_stoqb(m->dqb_bsoftlimit));
|
||||
d->dqb_curspace = cpu_to_le64(m->dqb_curspace);
|
||||
d->dqb_btime = cpu_to_le64(m->dqb_btime);
|
||||
d->dqb_id = cpu_to_le32(dquot->dq_id);
|
||||
if (qtree_entry_unused(info, dp))
|
||||
d->dqb_itime = cpu_to_le64(1);
|
||||
}
|
||||
|
||||
static int v2_is_id(void *dp, struct dquot *dquot)
|
||||
{
|
||||
struct v2_disk_dqblk *d = dp;
|
||||
struct qtree_mem_dqinfo *info =
|
||||
sb_dqinfo(dquot->dq_sb, dquot->dq_type)->dqi_priv;
|
||||
|
||||
if (qtree_entry_unused(info, dp))
|
||||
return 0;
|
||||
return le32_to_cpu(d->dqb_id) == dquot->dq_id;
|
||||
}
|
||||
|
||||
static int v2_read_dquot(struct dquot *dquot)
|
||||
{
|
||||
return qtree_read_dquot(sb_dqinfo(dquot->dq_sb, dquot->dq_type)->dqi_priv, dquot);
|
||||
}
|
||||
|
||||
static int v2_write_dquot(struct dquot *dquot)
|
||||
{
|
||||
return qtree_write_dquot(sb_dqinfo(dquot->dq_sb, dquot->dq_type)->dqi_priv, dquot);
|
||||
}
|
||||
|
||||
static int v2_release_dquot(struct dquot *dquot)
|
||||
{
|
||||
return qtree_release_dquot(sb_dqinfo(dquot->dq_sb, dquot->dq_type)->dqi_priv, dquot);
|
||||
}
|
||||
|
||||
static int v2_free_file_info(struct super_block *sb, int type)
|
||||
{
|
||||
kfree(sb_dqinfo(sb, type)->dqi_priv);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct quota_format_ops v2_format_ops = {
|
||||
.check_quota_file = v2_check_quota_file,
|
||||
.read_file_info = v2_read_file_info,
|
||||
.write_file_info = v2_write_file_info,
|
||||
.free_file_info = v2_free_file_info,
|
||||
.read_dqblk = v2_read_dquot,
|
||||
.commit_dqblk = v2_write_dquot,
|
||||
.release_dqblk = v2_release_dquot,
|
||||
};
|
||||
|
||||
static struct quota_format_type v2_quota_format = {
|
||||
.qf_fmt_id = QFMT_VFS_V0,
|
||||
.qf_ops = &v2_format_ops,
|
||||
.qf_owner = THIS_MODULE
|
||||
};
|
||||
|
||||
static int __init init_v2_quota_format(void)
|
||||
{
|
||||
return register_quota_format(&v2_quota_format);
|
||||
}
|
||||
|
||||
static void __exit exit_v2_quota_format(void)
|
||||
{
|
||||
unregister_quota_format(&v2_quota_format);
|
||||
}
|
||||
|
||||
module_init(init_v2_quota_format);
|
||||
module_exit(exit_v2_quota_format);
|
33
fs/quota/quotaio_v1.h
Fichier normal
33
fs/quota/quotaio_v1.h
Fichier normal
@@ -0,0 +1,33 @@
|
||||
#ifndef _LINUX_QUOTAIO_V1_H
|
||||
#define _LINUX_QUOTAIO_V1_H
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
/*
|
||||
* The following constants define the amount of time given a user
|
||||
* before the soft limits are treated as hard limits (usually resulting
|
||||
* in an allocation failure). The timer is started when the user crosses
|
||||
* their soft limit, it is reset when they go below their soft limit.
|
||||
*/
|
||||
#define MAX_IQ_TIME 604800 /* (7*24*60*60) 1 week */
|
||||
#define MAX_DQ_TIME 604800 /* (7*24*60*60) 1 week */
|
||||
|
||||
/*
|
||||
* The following structure defines the format of the disk quota file
|
||||
* (as it appears on disk) - the file is an array of these structures
|
||||
* indexed by user or group number.
|
||||
*/
|
||||
struct v1_disk_dqblk {
|
||||
__u32 dqb_bhardlimit; /* absolute limit on disk blks alloc */
|
||||
__u32 dqb_bsoftlimit; /* preferred limit on disk blks */
|
||||
__u32 dqb_curblocks; /* current block count */
|
||||
__u32 dqb_ihardlimit; /* absolute limit on allocated inodes */
|
||||
__u32 dqb_isoftlimit; /* preferred inode limit */
|
||||
__u32 dqb_curinodes; /* current # allocated inodes */
|
||||
time_t dqb_btime; /* time limit for excessive disk use */
|
||||
time_t dqb_itime; /* time limit for excessive inode use */
|
||||
};
|
||||
|
||||
#define v1_dqoff(UID) ((loff_t)((UID) * sizeof (struct v1_disk_dqblk)))
|
||||
|
||||
#endif /* _LINUX_QUOTAIO_V1_H */
|
60
fs/quota/quotaio_v2.h
Fichier normal
60
fs/quota/quotaio_v2.h
Fichier normal
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Definitions of structures for vfsv0 quota format
|
||||
*/
|
||||
|
||||
#ifndef _LINUX_QUOTAIO_V2_H
|
||||
#define _LINUX_QUOTAIO_V2_H
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/quota.h>
|
||||
|
||||
/*
|
||||
* Definitions of magics and versions of current quota files
|
||||
*/
|
||||
#define V2_INITQMAGICS {\
|
||||
0xd9c01f11, /* USRQUOTA */\
|
||||
0xd9c01927 /* GRPQUOTA */\
|
||||
}
|
||||
|
||||
#define V2_INITQVERSIONS {\
|
||||
0, /* USRQUOTA */\
|
||||
0 /* GRPQUOTA */\
|
||||
}
|
||||
|
||||
/* First generic header */
|
||||
struct v2_disk_dqheader {
|
||||
__le32 dqh_magic; /* Magic number identifying file */
|
||||
__le32 dqh_version; /* File version */
|
||||
};
|
||||
|
||||
/*
|
||||
* The following structure defines the format of the disk quota file
|
||||
* (as it appears on disk) - the file is a radix tree whose leaves point
|
||||
* to blocks of these structures.
|
||||
*/
|
||||
struct v2_disk_dqblk {
|
||||
__le32 dqb_id; /* id this quota applies to */
|
||||
__le32 dqb_ihardlimit; /* absolute limit on allocated inodes */
|
||||
__le32 dqb_isoftlimit; /* preferred inode limit */
|
||||
__le32 dqb_curinodes; /* current # allocated inodes */
|
||||
__le32 dqb_bhardlimit; /* absolute limit on disk space (in QUOTABLOCK_SIZE) */
|
||||
__le32 dqb_bsoftlimit; /* preferred limit on disk space (in QUOTABLOCK_SIZE) */
|
||||
__le64 dqb_curspace; /* current space occupied (in bytes) */
|
||||
__le64 dqb_btime; /* time limit for excessive disk use */
|
||||
__le64 dqb_itime; /* time limit for excessive inode use */
|
||||
};
|
||||
|
||||
/* Header with type and version specific information */
|
||||
struct v2_disk_dqinfo {
|
||||
__le32 dqi_bgrace; /* Time before block soft limit becomes hard limit */
|
||||
__le32 dqi_igrace; /* Time before inode soft limit becomes hard limit */
|
||||
__le32 dqi_flags; /* Flags for quotafile (DQF_*) */
|
||||
__le32 dqi_blocks; /* Number of blocks in file */
|
||||
__le32 dqi_free_blk; /* Number of first free block in the list */
|
||||
__le32 dqi_free_entry; /* Number of block with at least one free entry */
|
||||
};
|
||||
|
||||
#define V2_DQINFOOFF sizeof(struct v2_disk_dqheader) /* Offset of info header in file */
|
||||
#define V2_DQBLKSIZE_BITS 10 /* Size of leaf block in tree */
|
||||
|
||||
#endif /* _LINUX_QUOTAIO_V2_H */
|
Référencer dans un nouveau ticket
Bloquer un utilisateur