mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-07 00:38:05 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
75
fs/quota/Kconfig
Normal file
75
fs/quota/Kconfig
Normal 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
7
fs/quota/Makefile
Normal 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
118
fs/quota/compat.c
Normal 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
2738
fs/quota/dquot.c
Normal file
File diff suppressed because it is too large
Load diff
132
fs/quota/kqid.c
Normal file
132
fs/quota/kqid.c
Normal 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
109
fs/quota/netlink.c
Normal 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),
|
||||
"a_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("a_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("a_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
568
fs/quota/quota.c
Normal 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
663
fs/quota/quota_tree.c
Normal 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
25
fs/quota/quota_tree.h
Normal 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
235
fs/quota/quota_v1.c
Normal 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
340
fs/quota/quota_v2.c
Normal 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
33
fs/quota/quotaio_v1.h
Normal 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
73
fs/quota/quotaio_v2.h
Normal 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 */
|
Loading…
Add table
Add a link
Reference in a new issue