Fixed MTP to work with TWRP

This commit is contained in:
awab228 2018-06-19 23:16:04 +02:00
commit f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions

75
fs/quota/Kconfig Normal file
View file

@ -0,0 +1,75 @@
#
# Quota configuration
#
config QUOTA
bool "Quota support"
select QUOTACTL
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, ext4, jfs, ocfs2 and reiserfs file systems.
Note that gfs2 and xfs use their own quota system.
Ext3, ext4 and reiserfs also support journaled 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 QUOTACTL && 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.
config QUOTA_DEBUG
bool "Additional quota sanity checks"
depends on QUOTA
default n
help
If you say Y here, quota subsystem will perform some additional
sanity checks of quota internal structures. If unsure, say N.
# Generic support for tree structured quota files. Selected 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 vfsv0 and vfsv1 support"
depends on QUOTA
select QUOTA_TREE
help
This config option enables kernel support for vfsv0 and vfsv1 quota
formats. Both these formats support 32-bit UIDs/GIDs and vfsv1 format
also supports 64-bit inode and block quota limits. If you need this
functionality say Y here.
config QUOTACTL
bool
default n
config QUOTACTL_COMPAT
bool
depends on QUOTACTL && COMPAT_FOR_U64_ALIGNMENT
default y

7
fs/quota/Makefile Normal file
View file

@ -0,0 +1,7 @@
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 kqid.o
obj-$(CONFIG_QUOTACTL_COMPAT) += compat.o
obj-$(CONFIG_QUOTA_NETLINK_INTERFACE) += netlink.o

118
fs/quota/compat.c Normal file
View file

@ -0,0 +1,118 @@
#include <linux/syscalls.h>
#include <linux/compat.h>
#include <linux/quotaops.h>
/*
* 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;
}

2738
fs/quota/dquot.c Normal file

File diff suppressed because it is too large Load diff

132
fs/quota/kqid.c Normal file
View file

@ -0,0 +1,132 @@
#include <linux/fs.h>
#include <linux/quota.h>
#include <linux/export.h>
/**
* qid_eq - Test to see if to kquid values are the same
* @left: A qid value
* @right: Another quid value
*
* Return true if the two qid values are equal and false otherwise.
*/
bool qid_eq(struct kqid left, struct kqid right)
{
if (left.type != right.type)
return false;
switch(left.type) {
case USRQUOTA:
return uid_eq(left.uid, right.uid);
case GRPQUOTA:
return gid_eq(left.gid, right.gid);
case PRJQUOTA:
return projid_eq(left.projid, right.projid);
default:
BUG();
}
}
EXPORT_SYMBOL(qid_eq);
/**
* qid_lt - Test to see if one qid value is less than another
* @left: The possibly lesser qid value
* @right: The possibly greater qid value
*
* Return true if left is less than right and false otherwise.
*/
bool qid_lt(struct kqid left, struct kqid right)
{
if (left.type < right.type)
return true;
if (left.type > right.type)
return false;
switch (left.type) {
case USRQUOTA:
return uid_lt(left.uid, right.uid);
case GRPQUOTA:
return gid_lt(left.gid, right.gid);
case PRJQUOTA:
return projid_lt(left.projid, right.projid);
default:
BUG();
}
}
EXPORT_SYMBOL(qid_lt);
/**
* from_kqid - Create a qid from a kqid user-namespace pair.
* @targ: The user namespace we want a qid in.
* @kqid: The kernel internal quota identifier to start with.
*
* Map @kqid into the user-namespace specified by @targ and
* return the resulting qid.
*
* There is always a mapping into the initial user_namespace.
*
* If @kqid has no mapping in @targ (qid_t)-1 is returned.
*/
qid_t from_kqid(struct user_namespace *targ, struct kqid kqid)
{
switch (kqid.type) {
case USRQUOTA:
return from_kuid(targ, kqid.uid);
case GRPQUOTA:
return from_kgid(targ, kqid.gid);
case PRJQUOTA:
return from_kprojid(targ, kqid.projid);
default:
BUG();
}
}
EXPORT_SYMBOL(from_kqid);
/**
* from_kqid_munged - Create a qid from a kqid user-namespace pair.
* @targ: The user namespace we want a qid in.
* @kqid: The kernel internal quota identifier to start with.
*
* Map @kqid into the user-namespace specified by @targ and
* return the resulting qid.
*
* There is always a mapping into the initial user_namespace.
*
* Unlike from_kqid from_kqid_munged never fails and always
* returns a valid projid. This makes from_kqid_munged
* appropriate for use in places where failing to provide
* a qid_t is not a good option.
*
* If @kqid has no mapping in @targ the kqid.type specific
* overflow identifier is returned.
*/
qid_t from_kqid_munged(struct user_namespace *targ, struct kqid kqid)
{
switch (kqid.type) {
case USRQUOTA:
return from_kuid_munged(targ, kqid.uid);
case GRPQUOTA:
return from_kgid_munged(targ, kqid.gid);
case PRJQUOTA:
return from_kprojid_munged(targ, kqid.projid);
default:
BUG();
}
}
EXPORT_SYMBOL(from_kqid_munged);
/**
* qid_valid - Report if a valid value is stored in a kqid.
* @qid: The kernel internal quota identifier to test.
*/
bool qid_valid(struct kqid qid)
{
switch (qid.type) {
case USRQUOTA:
return uid_valid(qid.uid);
case GRPQUOTA:
return gid_valid(qid.gid);
case PRJQUOTA:
return projid_valid(qid.projid);
default:
BUG();
}
}
EXPORT_SYMBOL(qid_valid);

109
fs/quota/netlink.c Normal file
View file

@ -0,0 +1,109 @@
#include <linux/cred.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/quotaops.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <net/netlink.h>
#include <net/genetlink.h>
static const struct genl_multicast_group quota_mcgrps[] = {
{ .name = "events", },
};
/* Netlink family structure for quota */
static struct genl_family quota_genl_family = {
/*
* Needed due to multicast group ID abuse - old code assumed
* the family ID was also a valid multicast group ID (which
* isn't true) and userspace might thus rely on it. Assign a
* static ID for this group to make dealing with that easier.
*/
.id = GENL_ID_VFS_DQUOT,
.hdrsize = 0,
.name = "VFS_DQUOT",
.version = 1,
.maxattr = QUOTA_NL_A_MAX,
.mcgrps = quota_mcgrps,
.n_mcgrps = ARRAY_SIZE(quota_mcgrps),
};
/**
* quota_send_warning - Send warning to userspace about exceeded quota
* @qid: The kernel internal quota identifier.
* @dev: The device on which the fs is mounted (sb->s_dev)
* @warntype: The type of the warning: QUOTA_NL_...
*
* This can be used by filesystems (including those which don't use
* dquot) to send a message to userspace relating to quota limits.
*
*/
void quota_send_warning(struct kqid qid, dev_t dev,
const char warntype)
{
static atomic_t seq;
struct sk_buff *skb;
void *msg_head;
int ret;
int msg_size = 4 * nla_total_size(sizeof(u32)) +
2 * nla_total_size(sizeof(u64));
/* We have to allocate using GFP_NOFS as we are called from a
* filesystem performing write and thus further recursion into
* the fs to free some data could cause deadlocks. */
skb = genlmsg_new(msg_size, GFP_NOFS);
if (!skb) {
printk(KERN_ERR
"VFS: Not enough memory to send quota warning.\n");
return;
}
msg_head = genlmsg_put(skb, 0, atomic_add_return(1, &seq),
&quota_genl_family, 0, QUOTA_NL_C_WARNING);
if (!msg_head) {
printk(KERN_ERR
"VFS: Cannot store netlink header in quota warning.\n");
goto err_out;
}
ret = nla_put_u32(skb, QUOTA_NL_A_QTYPE, qid.type);
if (ret)
goto attr_err_out;
ret = nla_put_u64(skb, QUOTA_NL_A_EXCESS_ID,
from_kqid_munged(&init_user_ns, qid));
if (ret)
goto attr_err_out;
ret = nla_put_u32(skb, QUOTA_NL_A_WARNING, warntype);
if (ret)
goto attr_err_out;
ret = nla_put_u32(skb, QUOTA_NL_A_DEV_MAJOR, MAJOR(dev));
if (ret)
goto attr_err_out;
ret = nla_put_u32(skb, QUOTA_NL_A_DEV_MINOR, MINOR(dev));
if (ret)
goto attr_err_out;
ret = nla_put_u64(skb, QUOTA_NL_A_CAUSED_ID,
from_kuid_munged(&init_user_ns, current_uid()));
if (ret)
goto attr_err_out;
genlmsg_end(skb, msg_head);
genlmsg_multicast(&quota_genl_family, skb, 0, 0, GFP_NOFS);
return;
attr_err_out:
printk(KERN_ERR "VFS: Not enough space to compose quota message!\n");
err_out:
kfree_skb(skb);
}
EXPORT_SYMBOL(quota_send_warning);
static int __init quota_init(void)
{
if (genl_register_family(&quota_genl_family) != 0)
printk(KERN_ERR
"VFS: Failed to create quota netlink interface.\n");
return 0;
};
module_init(quota_init);

568
fs/quota/quota.c Normal file
View file

@ -0,0 +1,568 @@
/*
* 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 <linux/uaccess.h>
#include <linux/kernel.h>
#include <linux/security.h>
#include <linux/syscalls.h>
#include <linux/capability.h>
#include <linux/quotaops.h>
#include <linux/types.h>
#include <linux/writeback.h>
static int check_quotactl_permission(struct super_block *sb, int type, int cmd,
qid_t id)
{
switch (cmd) {
/* these commands do not require any special privilegues */
case Q_GETFMT:
case Q_SYNC:
case Q_GETINFO:
case Q_XGETQSTAT:
case Q_XGETQSTATV:
case Q_XQUOTASYNC:
break;
/* allow to query information for dquots we "own" */
case Q_GETQUOTA:
case Q_XGETQUOTA:
if ((type == USRQUOTA && uid_eq(current_euid(), make_kuid(current_user_ns(), id))) ||
(type == GRPQUOTA && in_egroup_p(make_kgid(current_user_ns(), id))))
break;
/*FALLTHROUGH*/
default:
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
}
return security_quotactl(cmd, type, id, sb);
}
static void quota_sync_one(struct super_block *sb, void *arg)
{
if (sb->s_qcop && sb->s_qcop->quota_sync)
sb->s_qcop->quota_sync(sb, *(int *)arg);
}
static int quota_sync_all(int type)
{
int ret;
if (type >= MAXQUOTAS)
return -EINVAL;
ret = security_quotactl(Q_SYNC, type, 0, NULL);
if (!ret)
iterate_supers(quota_sync_one, &type);
return ret;
}
static int quota_quotaon(struct super_block *sb, int type, int cmd, qid_t id,
struct path *path)
{
if (!sb->s_qcop->quota_on && !sb->s_qcop->quota_on_meta)
return -ENOSYS;
if (sb->s_qcop->quota_on_meta)
return sb->s_qcop->quota_on_meta(sb, type, id);
if (IS_ERR(path))
return PTR_ERR(path);
return sb->s_qcop->quota_on(sb, type, id, path);
}
static int quota_getfmt(struct super_block *sb, int type, void __user *addr)
{
__u32 fmt;
mutex_lock(&sb_dqopt(sb)->dqonoff_mutex);
if (!sb_has_quota_active(sb, type)) {
mutex_unlock(&sb_dqopt(sb)->dqonoff_mutex);
return -ESRCH;
}
fmt = sb_dqopt(sb)->info[type].dqi_format->qf_fmt_id;
mutex_unlock(&sb_dqopt(sb)->dqonoff_mutex);
if (copy_to_user(addr, &fmt, sizeof(fmt)))
return -EFAULT;
return 0;
}
static int quota_getinfo(struct super_block *sb, int type, void __user *addr)
{
struct if_dqinfo info;
int ret;
if (!sb->s_qcop->get_info)
return -ENOSYS;
ret = sb->s_qcop->get_info(sb, type, &info);
if (!ret && copy_to_user(addr, &info, sizeof(info)))
return -EFAULT;
return ret;
}
static int quota_setinfo(struct super_block *sb, int type, void __user *addr)
{
struct if_dqinfo info;
if (copy_from_user(&info, addr, sizeof(info)))
return -EFAULT;
if (!sb->s_qcop->set_info)
return -ENOSYS;
return sb->s_qcop->set_info(sb, type, &info);
}
static inline qsize_t qbtos(qsize_t blocks)
{
return blocks << QIF_DQBLKSIZE_BITS;
}
static inline qsize_t stoqb(qsize_t space)
{
return (space + QIF_DQBLKSIZE - 1) >> QIF_DQBLKSIZE_BITS;
}
static void copy_to_if_dqblk(struct if_dqblk *dst, struct qc_dqblk *src)
{
memset(dst, 0, sizeof(*dst));
dst->dqb_bhardlimit = stoqb(src->d_spc_hardlimit);
dst->dqb_bsoftlimit = stoqb(src->d_spc_softlimit);
dst->dqb_curspace = src->d_space;
dst->dqb_ihardlimit = src->d_ino_hardlimit;
dst->dqb_isoftlimit = src->d_ino_softlimit;
dst->dqb_curinodes = src->d_ino_count;
dst->dqb_btime = src->d_spc_timer;
dst->dqb_itime = src->d_ino_timer;
dst->dqb_valid = QIF_ALL;
}
static int quota_getquota(struct super_block *sb, int type, qid_t id,
void __user *addr)
{
struct kqid qid;
struct qc_dqblk fdq;
struct if_dqblk idq;
int ret;
if (!sb->s_qcop->get_dqblk)
return -ENOSYS;
qid = make_kqid(current_user_ns(), type, id);
if (!qid_valid(qid))
return -EINVAL;
ret = sb->s_qcop->get_dqblk(sb, qid, &fdq);
if (ret)
return ret;
copy_to_if_dqblk(&idq, &fdq);
if (copy_to_user(addr, &idq, sizeof(idq)))
return -EFAULT;
return 0;
}
static void copy_from_if_dqblk(struct qc_dqblk *dst, struct if_dqblk *src)
{
dst->d_spc_hardlimit = qbtos(src->dqb_bhardlimit);
dst->d_spc_softlimit = qbtos(src->dqb_bsoftlimit);
dst->d_space = src->dqb_curspace;
dst->d_ino_hardlimit = src->dqb_ihardlimit;
dst->d_ino_softlimit = src->dqb_isoftlimit;
dst->d_ino_count = src->dqb_curinodes;
dst->d_spc_timer = src->dqb_btime;
dst->d_ino_timer = src->dqb_itime;
dst->d_fieldmask = 0;
if (src->dqb_valid & QIF_BLIMITS)
dst->d_fieldmask |= QC_SPC_SOFT | QC_SPC_HARD;
if (src->dqb_valid & QIF_SPACE)
dst->d_fieldmask |= QC_SPACE;
if (src->dqb_valid & QIF_ILIMITS)
dst->d_fieldmask |= QC_INO_SOFT | QC_INO_HARD;
if (src->dqb_valid & QIF_INODES)
dst->d_fieldmask |= QC_INO_COUNT;
if (src->dqb_valid & QIF_BTIME)
dst->d_fieldmask |= QC_SPC_TIMER;
if (src->dqb_valid & QIF_ITIME)
dst->d_fieldmask |= QC_INO_TIMER;
}
static int quota_setquota(struct super_block *sb, int type, qid_t id,
void __user *addr)
{
struct qc_dqblk fdq;
struct if_dqblk idq;
struct kqid qid;
if (copy_from_user(&idq, addr, sizeof(idq)))
return -EFAULT;
if (!sb->s_qcop->set_dqblk)
return -ENOSYS;
qid = make_kqid(current_user_ns(), type, id);
if (!qid_valid(qid))
return -EINVAL;
copy_from_if_dqblk(&fdq, &idq);
return sb->s_qcop->set_dqblk(sb, qid, &fdq);
}
static int quota_setxstate(struct super_block *sb, int cmd, void __user *addr)
{
__u32 flags;
if (copy_from_user(&flags, addr, sizeof(flags)))
return -EFAULT;
if (!sb->s_qcop->set_xstate)
return -ENOSYS;
return sb->s_qcop->set_xstate(sb, flags, cmd);
}
static int quota_getxstate(struct super_block *sb, void __user *addr)
{
struct fs_quota_stat fqs;
int ret;
if (!sb->s_qcop->get_xstate)
return -ENOSYS;
ret = sb->s_qcop->get_xstate(sb, &fqs);
if (!ret && copy_to_user(addr, &fqs, sizeof(fqs)))
return -EFAULT;
return ret;
}
static int quota_getxstatev(struct super_block *sb, void __user *addr)
{
struct fs_quota_statv fqs;
int ret;
if (!sb->s_qcop->get_xstatev)
return -ENOSYS;
memset(&fqs, 0, sizeof(fqs));
if (copy_from_user(&fqs, addr, 1)) /* Just read qs_version */
return -EFAULT;
/* If this kernel doesn't support user specified version, fail */
switch (fqs.qs_version) {
case FS_QSTATV_VERSION1:
break;
default:
return -EINVAL;
}
ret = sb->s_qcop->get_xstatev(sb, &fqs);
if (!ret && copy_to_user(addr, &fqs, sizeof(fqs)))
return -EFAULT;
return ret;
}
/*
* XFS defines BBTOB and BTOBB macros inside fs/xfs/ and we cannot move them
* out of there as xfsprogs rely on definitions being in that header file. So
* just define same functions here for quota purposes.
*/
#define XFS_BB_SHIFT 9
static inline u64 quota_bbtob(u64 blocks)
{
return blocks << XFS_BB_SHIFT;
}
static inline u64 quota_btobb(u64 bytes)
{
return (bytes + (1 << XFS_BB_SHIFT) - 1) >> XFS_BB_SHIFT;
}
static void copy_from_xfs_dqblk(struct qc_dqblk *dst, struct fs_disk_quota *src)
{
dst->d_spc_hardlimit = quota_bbtob(src->d_blk_hardlimit);
dst->d_spc_softlimit = quota_bbtob(src->d_blk_softlimit);
dst->d_ino_hardlimit = src->d_ino_hardlimit;
dst->d_ino_softlimit = src->d_ino_softlimit;
dst->d_space = quota_bbtob(src->d_bcount);
dst->d_ino_count = src->d_icount;
dst->d_ino_timer = src->d_itimer;
dst->d_spc_timer = src->d_btimer;
dst->d_ino_warns = src->d_iwarns;
dst->d_spc_warns = src->d_bwarns;
dst->d_rt_spc_hardlimit = quota_bbtob(src->d_rtb_hardlimit);
dst->d_rt_spc_softlimit = quota_bbtob(src->d_rtb_softlimit);
dst->d_rt_space = quota_bbtob(src->d_rtbcount);
dst->d_rt_spc_timer = src->d_rtbtimer;
dst->d_rt_spc_warns = src->d_rtbwarns;
dst->d_fieldmask = 0;
if (src->d_fieldmask & FS_DQ_ISOFT)
dst->d_fieldmask |= QC_INO_SOFT;
if (src->d_fieldmask & FS_DQ_IHARD)
dst->d_fieldmask |= QC_INO_HARD;
if (src->d_fieldmask & FS_DQ_BSOFT)
dst->d_fieldmask |= QC_SPC_SOFT;
if (src->d_fieldmask & FS_DQ_BHARD)
dst->d_fieldmask |= QC_SPC_HARD;
if (src->d_fieldmask & FS_DQ_RTBSOFT)
dst->d_fieldmask |= QC_RT_SPC_SOFT;
if (src->d_fieldmask & FS_DQ_RTBHARD)
dst->d_fieldmask |= QC_RT_SPC_HARD;
if (src->d_fieldmask & FS_DQ_BTIMER)
dst->d_fieldmask |= QC_SPC_TIMER;
if (src->d_fieldmask & FS_DQ_ITIMER)
dst->d_fieldmask |= QC_INO_TIMER;
if (src->d_fieldmask & FS_DQ_RTBTIMER)
dst->d_fieldmask |= QC_RT_SPC_TIMER;
if (src->d_fieldmask & FS_DQ_BWARNS)
dst->d_fieldmask |= QC_SPC_WARNS;
if (src->d_fieldmask & FS_DQ_IWARNS)
dst->d_fieldmask |= QC_INO_WARNS;
if (src->d_fieldmask & FS_DQ_RTBWARNS)
dst->d_fieldmask |= QC_RT_SPC_WARNS;
if (src->d_fieldmask & FS_DQ_BCOUNT)
dst->d_fieldmask |= QC_SPACE;
if (src->d_fieldmask & FS_DQ_ICOUNT)
dst->d_fieldmask |= QC_INO_COUNT;
if (src->d_fieldmask & FS_DQ_RTBCOUNT)
dst->d_fieldmask |= QC_RT_SPACE;
}
static int quota_setxquota(struct super_block *sb, int type, qid_t id,
void __user *addr)
{
struct fs_disk_quota fdq;
struct qc_dqblk qdq;
struct kqid qid;
if (copy_from_user(&fdq, addr, sizeof(fdq)))
return -EFAULT;
if (!sb->s_qcop->set_dqblk)
return -ENOSYS;
qid = make_kqid(current_user_ns(), type, id);
if (!qid_valid(qid))
return -EINVAL;
copy_from_xfs_dqblk(&qdq, &fdq);
return sb->s_qcop->set_dqblk(sb, qid, &qdq);
}
static void copy_to_xfs_dqblk(struct fs_disk_quota *dst, struct qc_dqblk *src,
int type, qid_t id)
{
memset(dst, 0, sizeof(*dst));
dst->d_version = FS_DQUOT_VERSION;
dst->d_id = id;
if (type == USRQUOTA)
dst->d_flags = FS_USER_QUOTA;
else if (type == PRJQUOTA)
dst->d_flags = FS_PROJ_QUOTA;
else
dst->d_flags = FS_GROUP_QUOTA;
dst->d_blk_hardlimit = quota_btobb(src->d_spc_hardlimit);
dst->d_blk_softlimit = quota_btobb(src->d_spc_softlimit);
dst->d_ino_hardlimit = src->d_ino_hardlimit;
dst->d_ino_softlimit = src->d_ino_softlimit;
dst->d_bcount = quota_btobb(src->d_space);
dst->d_icount = src->d_ino_count;
dst->d_itimer = src->d_ino_timer;
dst->d_btimer = src->d_spc_timer;
dst->d_iwarns = src->d_ino_warns;
dst->d_bwarns = src->d_spc_warns;
dst->d_rtb_hardlimit = quota_btobb(src->d_rt_spc_hardlimit);
dst->d_rtb_softlimit = quota_btobb(src->d_rt_spc_softlimit);
dst->d_rtbcount = quota_btobb(src->d_rt_space);
dst->d_rtbtimer = src->d_rt_spc_timer;
dst->d_rtbwarns = src->d_rt_spc_warns;
}
static int quota_getxquota(struct super_block *sb, int type, qid_t id,
void __user *addr)
{
struct fs_disk_quota fdq;
struct qc_dqblk qdq;
struct kqid qid;
int ret;
if (!sb->s_qcop->get_dqblk)
return -ENOSYS;
qid = make_kqid(current_user_ns(), type, id);
if (!qid_valid(qid))
return -EINVAL;
ret = sb->s_qcop->get_dqblk(sb, qid, &qdq);
if (ret)
return ret;
copy_to_xfs_dqblk(&fdq, &qdq, type, id);
if (copy_to_user(addr, &fdq, sizeof(fdq)))
return -EFAULT;
return ret;
}
static int quota_rmxquota(struct super_block *sb, void __user *addr)
{
__u32 flags;
if (copy_from_user(&flags, addr, sizeof(flags)))
return -EFAULT;
if (!sb->s_qcop->rm_xquota)
return -ENOSYS;
return sb->s_qcop->rm_xquota(sb, flags);
}
/* Copy parameters and call proper function */
static int do_quotactl(struct super_block *sb, int type, int cmd, qid_t id,
void __user *addr, struct path *path)
{
int ret;
if (type >= (XQM_COMMAND(cmd) ? XQM_MAXQUOTAS : MAXQUOTAS))
return -EINVAL;
if (!sb->s_qcop)
return -ENOSYS;
ret = check_quotactl_permission(sb, type, cmd, id);
if (ret < 0)
return ret;
switch (cmd) {
case Q_QUOTAON:
return quota_quotaon(sb, type, cmd, id, path);
case Q_QUOTAOFF:
if (!sb->s_qcop->quota_off)
return -ENOSYS;
return sb->s_qcop->quota_off(sb, type);
case Q_GETFMT:
return quota_getfmt(sb, type, addr);
case Q_GETINFO:
return quota_getinfo(sb, type, addr);
case Q_SETINFO:
return quota_setinfo(sb, type, addr);
case Q_GETQUOTA:
return quota_getquota(sb, type, id, addr);
case Q_SETQUOTA:
return quota_setquota(sb, type, id, addr);
case Q_SYNC:
if (!sb->s_qcop->quota_sync)
return -ENOSYS;
return sb->s_qcop->quota_sync(sb, type);
case Q_XQUOTAON:
case Q_XQUOTAOFF:
return quota_setxstate(sb, cmd, addr);
case Q_XQUOTARM:
return quota_rmxquota(sb, addr);
case Q_XGETQSTAT:
return quota_getxstate(sb, addr);
case Q_XGETQSTATV:
return quota_getxstatev(sb, addr);
case Q_XSETQLIM:
return quota_setxquota(sb, type, id, addr);
case Q_XGETQUOTA:
return quota_getxquota(sb, type, id, addr);
case Q_XQUOTASYNC:
if (sb->s_flags & MS_RDONLY)
return -EROFS;
/* XFS quotas are fully coherent now, making this call a noop */
return 0;
default:
return -EINVAL;
}
}
#ifdef CONFIG_BLOCK
/* Return 1 if 'cmd' will block on frozen filesystem */
static int quotactl_cmd_write(int cmd)
{
switch (cmd) {
case Q_GETFMT:
case Q_GETINFO:
case Q_SYNC:
case Q_XGETQSTAT:
case Q_XGETQSTATV:
case Q_XGETQUOTA:
case Q_XQUOTASYNC:
return 0;
}
return 1;
}
#endif /* CONFIG_BLOCK */
/*
* look up a superblock on which quota ops will be performed
* - use the name of a block device to find the superblock thereon
*/
static struct super_block *quotactl_block(const char __user *special, int cmd)
{
#ifdef CONFIG_BLOCK
struct block_device *bdev;
struct super_block *sb;
struct filename *tmp = getname(special);
if (IS_ERR(tmp))
return ERR_CAST(tmp);
bdev = lookup_bdev(tmp->name);
putname(tmp);
if (IS_ERR(bdev))
return ERR_CAST(bdev);
if (quotactl_cmd_write(cmd))
sb = get_super_thawed(bdev);
else
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;
struct path path, *pathp = NULL;
int ret;
cmds = cmd >> SUBCMDSHIFT;
type = cmd & SUBCMDMASK;
/*
* As a special case Q_SYNC can be called without a specific device.
* It will iterate all superblocks that have quota enabled and call
* the sync action on each of them.
*/
if (!special) {
if (cmds == Q_SYNC)
return quota_sync_all(type);
return -ENODEV;
}
/*
* Path for quotaon has to be resolved before grabbing superblock
* because that gets s_umount sem which is also possibly needed by path
* resolution (think about autofs) and thus deadlocks could arise.
*/
if (cmds == Q_QUOTAON) {
ret = user_path_at(AT_FDCWD, addr, LOOKUP_FOLLOW|LOOKUP_AUTOMOUNT, &path);
if (ret)
pathp = ERR_PTR(ret);
else
pathp = &path;
}
sb = quotactl_block(special, cmds);
if (IS_ERR(sb)) {
ret = PTR_ERR(sb);
goto out;
}
ret = do_quotactl(sb, type, cmds, id, addr, pathp);
drop_super(sb);
out:
if (pathp && !IS_ERR(pathp))
path_put(pathp);
return ret;
}

663
fs/quota/quota_tree.c Normal file
View file

@ -0,0 +1,663 @@
/*
* 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
static int get_index(struct qtree_mem_dqinfo *info, struct kqid qid, int depth)
{
unsigned int epb = info->dqi_usable_bs >> 2;
qid_t id = from_kqid(&init_user_ns, qid);
depth = info->dqi_qtree_depth - depth - 1;
while (depth--)
id /= epb;
return id % epb;
}
/* Number of entries in one blocks */
static 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 char *getdqbuf(size_t size)
{
char *buf = kmalloc(size, GFP_NOFS);
if (!buf)
printk(KERN_WARNING
"VFS: Not enough memory for quota buffers.\n");
return buf;
}
static ssize_t read_blk(struct qtree_mem_dqinfo *info, uint blk, char *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, buf,
info->dqi_usable_bs, blk << info->dqi_blocksize_bits);
}
static ssize_t write_blk(struct qtree_mem_dqinfo *info, uint blk, char *buf)
{
struct super_block *sb = info->dqi_sb;
ssize_t ret;
ret = sb->s_op->quota_write(sb, info->dqi_type, buf,
info->dqi_usable_bs, blk << info->dqi_blocksize_bits);
if (ret != info->dqi_usable_bs) {
quota_error(sb, "dquota write failed");
if (ret >= 0)
ret = -EIO;
}
return ret;
}
/* Remove empty block from list and return it */
static int get_free_dqblk(struct qtree_mem_dqinfo *info)
{
char *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:
kfree(buf);
return ret;
}
/* Insert empty block to the list */
static int put_free_dqblk(struct qtree_mem_dqinfo *info, char *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, char *buf,
uint blk)
{
char *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);
}
kfree(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)
quota_error(info->dqi_sb, "Can't write block (%u) "
"with free entries", blk);
return 0;
out_buf:
kfree(tmpbuf);
return err;
}
/* Insert given block to the beginning of list with free entries */
static int insert_free_dqentry(struct qtree_mem_dqinfo *info, char *buf,
uint blk)
{
char *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;
}
kfree(tmpbuf);
info->dqi_free_entry = blk;
mark_info_dirty(info->dqi_sb, info->dqi_type);
return 0;
out_buf:
kfree(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;
char *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;
kfree(buf);
return 0;
}
memset(buf, 0, info->dqi_usable_bs);
/* This is enough as the block is already zeroed and the entry
* list is empty... */
info->dqi_free_entry = blk;
mark_info_dirty(dquot->dq_sb, dquot->dq_id.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) {
quota_error(dquot->dq_sb, "Can't remove block (%u) "
"from entry free list", blk);
goto out_buf;
}
}
le16_add_cpu(&dh->dqdh_entries, 1);
/* Find free structure in block */
ddquot = buf + sizeof(struct qt_disk_dqdbheader);
for (i = 0; i < qtree_dqstr_in_blk(info); i++) {
if (qtree_entry_unused(info, ddquot))
break;
ddquot += info->dqi_entry_size;
}
#ifdef __QUOTA_QT_PARANOIA
if (i == qtree_dqstr_in_blk(info)) {
quota_error(dquot->dq_sb, "Data block full but it shouldn't");
*err = -EIO;
goto out_buf;
}
#endif
*err = write_blk(info, blk, buf);
if (*err < 0) {
quota_error(dquot->dq_sb, "Can't write quota data block %u",
blk);
goto out_buf;
}
dquot->dq_off = (blk << info->dqi_blocksize_bits) +
sizeof(struct qt_disk_dqdbheader) +
i * info->dqi_entry_size;
kfree(buf);
return blk;
out_buf:
kfree(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)
{
char *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) {
quota_error(dquot->dq_sb, "Can't read tree quota "
"block %u", *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) {
quota_error(dquot->dq_sb, "Inserting already present "
"quota entry (block %u)",
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:
kfree(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_id.type;
struct super_block *sb = dquot->dq_sb;
ssize_t ret;
char *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) {
quota_error(sb, "Error %zd occurred while creating "
"quota", ret);
kfree(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, ddquot, info->dqi_entry_size,
dquot->dq_off);
if (ret != info->dqi_entry_size) {
quota_error(sb, "dquota write failed");
if (ret >= 0)
ret = -ENOSPC;
} else {
ret = 0;
}
dqstats_inc(DQST_WRITES);
kfree(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;
char *buf = getdqbuf(info->dqi_usable_bs);
int ret = 0;
if (!buf)
return -ENOMEM;
if (dquot->dq_off >> info->dqi_blocksize_bits != blk) {
quota_error(dquot->dq_sb, "Quota structure has offset to "
"other block (%u) than it should (%u)", blk,
(uint)(dquot->dq_off >> info->dqi_blocksize_bits));
goto out_buf;
}
ret = read_blk(info, blk, buf);
if (ret < 0) {
quota_error(dquot->dq_sb, "Can't read quota data block %u",
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) {
quota_error(dquot->dq_sb, "Can't move quota data block "
"(%u) to free list", 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) {
quota_error(dquot->dq_sb, "Can't insert quota "
"data block (%u) to free entry list", blk);
goto out_buf;
}
} else {
ret = write_blk(info, blk, buf);
if (ret < 0) {
quota_error(dquot->dq_sb, "Can't write quota "
"data block %u", blk);
goto out_buf;
}
}
}
dquot->dq_off = 0; /* Quota is now unattached */
out_buf:
kfree(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)
{
char *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) {
quota_error(dquot->dq_sb, "Can't read quota data block %u",
*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)
quota_error(dquot->dq_sb,
"Can't write quota tree block %u",
*blk);
}
}
out_buf:
kfree(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)
{
char *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) {
quota_error(dquot->dq_sb, "Can't read quota tree "
"block %u", blk);
goto out_buf;
}
ddquot = buf + sizeof(struct qt_disk_dqdbheader);
for (i = 0; i < qtree_dqstr_in_blk(info); i++) {
if (info->dqi_ops->is_id(ddquot, dquot))
break;
ddquot += info->dqi_entry_size;
}
if (i == qtree_dqstr_in_blk(info)) {
quota_error(dquot->dq_sb,
"Quota for id %u referenced but not present",
from_kqid(&init_user_ns, 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:
kfree(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)
{
char *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) {
quota_error(dquot->dq_sb, "Can't read quota tree block %u",
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:
kfree(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_id.type;
struct super_block *sb = dquot->dq_sb;
loff_t offset;
char *ddquot;
int ret = 0;
#ifdef __QUOTA_QT_PARANOIA
/* Invalidated quota? */
if (!sb_dqopt(dquot->dq_sb)->files[type]) {
quota_error(sb, "Quota invalidated while reading!");
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)
quota_error(sb,"Can't read quota structure "
"for id %u",
from_kqid(&init_user_ns,
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, ddquot, info->dqi_entry_size,
dquot->dq_off);
if (ret != info->dqi_entry_size) {
if (ret >= 0)
ret = -EIO;
quota_error(sb, "Error while reading quota structure for id %u",
from_kqid(&init_user_ns, dquot->dq_id));
set_bit(DQ_FAKE_B, &dquot->dq_flags);
memset(&dquot->dq_dqb, 0, sizeof(struct mem_dqblk));
kfree(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);
kfree(ddquot);
out:
dqstats_inc(DQST_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 Normal file
View file

@ -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 */

235
fs/quota/quota_v1.c Normal file
View file

@ -0,0 +1,235 @@
#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_id.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(from_kqid(&init_user_ns, 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_inc(DQST_READS);
return 0;
}
static int v1_commit_dqblk(struct dquot *dquot)
{
short type = dquot->dq_id.type;
ssize_t ret;
struct v1_disk_dqblk dqblk;
v1_mem2disk_dqblk(&dqblk, &dquot->dq_dqb);
if (((type == USRQUOTA) && uid_eq(dquot->dq_id.uid, GLOBAL_ROOT_UID)) ||
((type == GRPQUOTA) && gid_eq(dquot->dq_id.gid, GLOBAL_ROOT_GID))) {
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(from_kqid(&init_user_ns, dquot->dq_id)));
if (ret != sizeof(struct v1_disk_dqblk)) {
quota_error(dquot->dq_sb, "dquota write failed");
if (ret >= 0)
ret = -EIO;
goto out;
}
ret = 0;
out:
dqstats_inc(DQST_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;
ret = sb->s_op->quota_read(sb, type, (char *)&dqblk,
sizeof(struct v1_disk_dqblk), v1_dqoff(0));
if (ret != 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_max_spc_limit = 0xffffffffULL << QUOTABLOCK_BITS;
dqopt->info[type].dqi_max_ino_limit = 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;
ret = sb->s_op->quota_read(sb, type, (char *)&dqblk,
sizeof(struct v1_disk_dqblk), v1_dqoff(0));
if (ret != 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 const 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);

340
fs/quota/quota_v2.c Normal file
View file

@ -0,0 +1,340 @@
/*
* 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 v2r0_mem2diskdqb(void *dp, struct dquot *dquot);
static void v2r0_disk2memdqb(struct dquot *dquot, void *dp);
static int v2r0_is_id(void *dp, struct dquot *dquot);
static void v2r1_mem2diskdqb(void *dp, struct dquot *dquot);
static void v2r1_disk2memdqb(struct dquot *dquot, void *dp);
static int v2r1_is_id(void *dp, struct dquot *dquot);
static struct qtree_fmt_operations v2r0_qtree_ops = {
.mem2disk_dqblk = v2r0_mem2diskdqb,
.disk2mem_dqblk = v2r0_disk2memdqb,
.is_id = v2r0_is_id,
};
static struct qtree_fmt_operations v2r1_qtree_ops = {
.mem2disk_dqblk = v2r1_mem2diskdqb,
.disk2mem_dqblk = v2r1_disk2memdqb,
.is_id = v2r1_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;
}
static int v2_read_header(struct super_block *sb, int type,
struct v2_disk_dqheader *dqhead)
{
ssize_t size;
size = sb->s_op->quota_read(sb, type, (char *)dqhead,
sizeof(struct v2_disk_dqheader), 0);
if (size != sizeof(struct v2_disk_dqheader)) {
quota_error(sb, "Failed header read: expected=%zd got=%zd",
sizeof(struct v2_disk_dqheader), size);
return 0;
}
return 1;
}
/* 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;
static const uint quota_magics[] = V2_INITQMAGICS;
static const uint quota_versions[] = V2_INITQVERSIONS;
if (!v2_read_header(sb, type, &dqhead))
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 v2_disk_dqheader dqhead;
struct mem_dqinfo *info = sb_dqinfo(sb, type);
struct qtree_mem_dqinfo *qinfo;
ssize_t size;
unsigned int version;
if (!v2_read_header(sb, type, &dqhead))
return -1;
version = le32_to_cpu(dqhead.dqh_version);
if ((info->dqi_fmt_id == QFMT_VFS_V0 && version != 0) ||
(info->dqi_fmt_id == QFMT_VFS_V1 && version != 1))
return -1;
size = sb->s_op->quota_read(sb, type, (char *)&dinfo,
sizeof(struct v2_disk_dqinfo), V2_DQINFOOFF);
if (size != sizeof(struct v2_disk_dqinfo)) {
quota_error(sb, "Can't read info structure");
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 -ENOMEM;
}
qinfo = info->dqi_priv;
if (version == 0) {
/* limits are stored as unsigned 32-bit data */
info->dqi_max_spc_limit = 0xffffffffULL << QUOTABLOCK_BITS;
info->dqi_max_ino_limit = 0xffffffff;
} else {
/* used space is stored as unsigned 64-bit value in bytes */
info->dqi_max_spc_limit = 0xffffffffffffffffULL; /* 2^64-1 */
info->dqi_max_ino_limit = 0xffffffffffffffffULL;
}
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);
if (version == 0) {
qinfo->dqi_entry_size = sizeof(struct v2r0_disk_dqblk);
qinfo->dqi_ops = &v2r0_qtree_ops;
} else {
qinfo->dqi_entry_size = sizeof(struct v2r1_disk_dqblk);
qinfo->dqi_ops = &v2r1_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)) {
quota_error(sb, "Can't write info structure");
return -1;
}
return 0;
}
static void v2r0_disk2memdqb(struct dquot *dquot, void *dp)
{
struct v2r0_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 v2r0_disk_dqblk));
empty.dqb_itime = cpu_to_le64(1);
if (!memcmp(&empty, dp, sizeof(struct v2r0_disk_dqblk)))
m->dqb_itime = 0;
}
static void v2r0_mem2diskdqb(void *dp, struct dquot *dquot)
{
struct v2r0_disk_dqblk *d = dp;
struct mem_dqblk *m = &dquot->dq_dqb;
struct qtree_mem_dqinfo *info =
sb_dqinfo(dquot->dq_sb, dquot->dq_id.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(from_kqid(&init_user_ns, dquot->dq_id));
if (qtree_entry_unused(info, dp))
d->dqb_itime = cpu_to_le64(1);
}
static int v2r0_is_id(void *dp, struct dquot *dquot)
{
struct v2r0_disk_dqblk *d = dp;
struct qtree_mem_dqinfo *info =
sb_dqinfo(dquot->dq_sb, dquot->dq_id.type)->dqi_priv;
if (qtree_entry_unused(info, dp))
return 0;
return qid_eq(make_kqid(&init_user_ns, dquot->dq_id.type,
le32_to_cpu(d->dqb_id)),
dquot->dq_id);
}
static void v2r1_disk2memdqb(struct dquot *dquot, void *dp)
{
struct v2r1_disk_dqblk *d = dp, empty;
struct mem_dqblk *m = &dquot->dq_dqb;
m->dqb_ihardlimit = le64_to_cpu(d->dqb_ihardlimit);
m->dqb_isoftlimit = le64_to_cpu(d->dqb_isoftlimit);
m->dqb_curinodes = le64_to_cpu(d->dqb_curinodes);
m->dqb_itime = le64_to_cpu(d->dqb_itime);
m->dqb_bhardlimit = v2_qbtos(le64_to_cpu(d->dqb_bhardlimit));
m->dqb_bsoftlimit = v2_qbtos(le64_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 v2r1_disk_dqblk));
empty.dqb_itime = cpu_to_le64(1);
if (!memcmp(&empty, dp, sizeof(struct v2r1_disk_dqblk)))
m->dqb_itime = 0;
}
static void v2r1_mem2diskdqb(void *dp, struct dquot *dquot)
{
struct v2r1_disk_dqblk *d = dp;
struct mem_dqblk *m = &dquot->dq_dqb;
struct qtree_mem_dqinfo *info =
sb_dqinfo(dquot->dq_sb, dquot->dq_id.type)->dqi_priv;
d->dqb_ihardlimit = cpu_to_le64(m->dqb_ihardlimit);
d->dqb_isoftlimit = cpu_to_le64(m->dqb_isoftlimit);
d->dqb_curinodes = cpu_to_le64(m->dqb_curinodes);
d->dqb_itime = cpu_to_le64(m->dqb_itime);
d->dqb_bhardlimit = cpu_to_le64(v2_stoqb(m->dqb_bhardlimit));
d->dqb_bsoftlimit = cpu_to_le64(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(from_kqid(&init_user_ns, dquot->dq_id));
if (qtree_entry_unused(info, dp))
d->dqb_itime = cpu_to_le64(1);
}
static int v2r1_is_id(void *dp, struct dquot *dquot)
{
struct v2r1_disk_dqblk *d = dp;
struct qtree_mem_dqinfo *info =
sb_dqinfo(dquot->dq_sb, dquot->dq_id.type)->dqi_priv;
if (qtree_entry_unused(info, dp))
return 0;
return qid_eq(make_kqid(&init_user_ns, dquot->dq_id.type,
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_id.type)->dqi_priv, dquot);
}
static int v2_write_dquot(struct dquot *dquot)
{
return qtree_write_dquot(sb_dqinfo(dquot->dq_sb, dquot->dq_id.type)->dqi_priv, dquot);
}
static int v2_release_dquot(struct dquot *dquot)
{
return qtree_release_dquot(sb_dqinfo(dquot->dq_sb, dquot->dq_id.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 const 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 v2r0_quota_format = {
.qf_fmt_id = QFMT_VFS_V0,
.qf_ops = &v2_format_ops,
.qf_owner = THIS_MODULE
};
static struct quota_format_type v2r1_quota_format = {
.qf_fmt_id = QFMT_VFS_V1,
.qf_ops = &v2_format_ops,
.qf_owner = THIS_MODULE
};
static int __init init_v2_quota_format(void)
{
int ret;
ret = register_quota_format(&v2r0_quota_format);
if (ret)
return ret;
return register_quota_format(&v2r1_quota_format);
}
static void __exit exit_v2_quota_format(void)
{
unregister_quota_format(&v2r0_quota_format);
unregister_quota_format(&v2r1_quota_format);
}
module_init(init_v2_quota_format);
module_exit(exit_v2_quota_format);

33
fs/quota/quotaio_v1.h Normal file
View file

@ -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 */

73
fs/quota/quotaio_v2.h Normal file
View file

@ -0,0 +1,73 @@
/*
* 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 {\
1, /* USRQUOTA */\
1 /* 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 v2r0_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 */
};
struct v2r1_disk_dqblk {
__le32 dqb_id; /* id this quota applies to */
__le32 dqb_pad;
__le64 dqb_ihardlimit; /* absolute limit on allocated inodes */
__le64 dqb_isoftlimit; /* preferred inode limit */
__le64 dqb_curinodes; /* current # allocated inodes */
__le64 dqb_bhardlimit; /* absolute limit on disk space (in QUOTABLOCK_SIZE) */
__le64 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 */