162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci#include <linux/errno.h>
362306a36Sopenharmony_ci#include <linux/fs.h>
462306a36Sopenharmony_ci#include <linux/quota.h>
562306a36Sopenharmony_ci#include <linux/quotaops.h>
662306a36Sopenharmony_ci#include <linux/dqblk_v1.h>
762306a36Sopenharmony_ci#include <linux/kernel.h>
862306a36Sopenharmony_ci#include <linux/init.h>
962306a36Sopenharmony_ci#include <linux/module.h>
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#include <asm/byteorder.h>
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#include "quotaio_v1.h"
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ciMODULE_AUTHOR("Jan Kara");
1662306a36Sopenharmony_ciMODULE_DESCRIPTION("Old quota format support");
1762306a36Sopenharmony_ciMODULE_LICENSE("GPL");
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci#define QUOTABLOCK_BITS 10
2062306a36Sopenharmony_ci#define QUOTABLOCK_SIZE (1 << QUOTABLOCK_BITS)
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_cistatic inline qsize_t v1_stoqb(qsize_t space)
2362306a36Sopenharmony_ci{
2462306a36Sopenharmony_ci	return (space + QUOTABLOCK_SIZE - 1) >> QUOTABLOCK_BITS;
2562306a36Sopenharmony_ci}
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_cistatic inline qsize_t v1_qbtos(qsize_t blocks)
2862306a36Sopenharmony_ci{
2962306a36Sopenharmony_ci	return blocks << QUOTABLOCK_BITS;
3062306a36Sopenharmony_ci}
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_cistatic void v1_disk2mem_dqblk(struct mem_dqblk *m, struct v1_disk_dqblk *d)
3362306a36Sopenharmony_ci{
3462306a36Sopenharmony_ci	m->dqb_ihardlimit = d->dqb_ihardlimit;
3562306a36Sopenharmony_ci	m->dqb_isoftlimit = d->dqb_isoftlimit;
3662306a36Sopenharmony_ci	m->dqb_curinodes = d->dqb_curinodes;
3762306a36Sopenharmony_ci	m->dqb_bhardlimit = v1_qbtos(d->dqb_bhardlimit);
3862306a36Sopenharmony_ci	m->dqb_bsoftlimit = v1_qbtos(d->dqb_bsoftlimit);
3962306a36Sopenharmony_ci	m->dqb_curspace = v1_qbtos(d->dqb_curblocks);
4062306a36Sopenharmony_ci	m->dqb_itime = d->dqb_itime;
4162306a36Sopenharmony_ci	m->dqb_btime = d->dqb_btime;
4262306a36Sopenharmony_ci}
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_cistatic void v1_mem2disk_dqblk(struct v1_disk_dqblk *d, struct mem_dqblk *m)
4562306a36Sopenharmony_ci{
4662306a36Sopenharmony_ci	d->dqb_ihardlimit = m->dqb_ihardlimit;
4762306a36Sopenharmony_ci	d->dqb_isoftlimit = m->dqb_isoftlimit;
4862306a36Sopenharmony_ci	d->dqb_curinodes = m->dqb_curinodes;
4962306a36Sopenharmony_ci	d->dqb_bhardlimit = v1_stoqb(m->dqb_bhardlimit);
5062306a36Sopenharmony_ci	d->dqb_bsoftlimit = v1_stoqb(m->dqb_bsoftlimit);
5162306a36Sopenharmony_ci	d->dqb_curblocks = v1_stoqb(m->dqb_curspace);
5262306a36Sopenharmony_ci	d->dqb_itime = m->dqb_itime;
5362306a36Sopenharmony_ci	d->dqb_btime = m->dqb_btime;
5462306a36Sopenharmony_ci}
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_cistatic int v1_read_dqblk(struct dquot *dquot)
5762306a36Sopenharmony_ci{
5862306a36Sopenharmony_ci	int type = dquot->dq_id.type;
5962306a36Sopenharmony_ci	struct v1_disk_dqblk dqblk;
6062306a36Sopenharmony_ci	struct quota_info *dqopt = sb_dqopt(dquot->dq_sb);
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	if (!dqopt->files[type])
6362306a36Sopenharmony_ci		return -EINVAL;
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	/* Set structure to 0s in case read fails/is after end of file */
6662306a36Sopenharmony_ci	memset(&dqblk, 0, sizeof(struct v1_disk_dqblk));
6762306a36Sopenharmony_ci	dquot->dq_sb->s_op->quota_read(dquot->dq_sb, type, (char *)&dqblk,
6862306a36Sopenharmony_ci			sizeof(struct v1_disk_dqblk),
6962306a36Sopenharmony_ci			v1_dqoff(from_kqid(&init_user_ns, dquot->dq_id)));
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	v1_disk2mem_dqblk(&dquot->dq_dqb, &dqblk);
7262306a36Sopenharmony_ci	if (dquot->dq_dqb.dqb_bhardlimit == 0 &&
7362306a36Sopenharmony_ci	    dquot->dq_dqb.dqb_bsoftlimit == 0 &&
7462306a36Sopenharmony_ci	    dquot->dq_dqb.dqb_ihardlimit == 0 &&
7562306a36Sopenharmony_ci	    dquot->dq_dqb.dqb_isoftlimit == 0)
7662306a36Sopenharmony_ci		set_bit(DQ_FAKE_B, &dquot->dq_flags);
7762306a36Sopenharmony_ci	dqstats_inc(DQST_READS);
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci	return 0;
8062306a36Sopenharmony_ci}
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_cistatic int v1_commit_dqblk(struct dquot *dquot)
8362306a36Sopenharmony_ci{
8462306a36Sopenharmony_ci	short type = dquot->dq_id.type;
8562306a36Sopenharmony_ci	ssize_t ret;
8662306a36Sopenharmony_ci	struct v1_disk_dqblk dqblk;
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	v1_mem2disk_dqblk(&dqblk, &dquot->dq_dqb);
8962306a36Sopenharmony_ci	if (((type == USRQUOTA) && uid_eq(dquot->dq_id.uid, GLOBAL_ROOT_UID)) ||
9062306a36Sopenharmony_ci	    ((type == GRPQUOTA) && gid_eq(dquot->dq_id.gid, GLOBAL_ROOT_GID))) {
9162306a36Sopenharmony_ci		dqblk.dqb_btime =
9262306a36Sopenharmony_ci			sb_dqopt(dquot->dq_sb)->info[type].dqi_bgrace;
9362306a36Sopenharmony_ci		dqblk.dqb_itime =
9462306a36Sopenharmony_ci			sb_dqopt(dquot->dq_sb)->info[type].dqi_igrace;
9562306a36Sopenharmony_ci	}
9662306a36Sopenharmony_ci	ret = 0;
9762306a36Sopenharmony_ci	if (sb_dqopt(dquot->dq_sb)->files[type])
9862306a36Sopenharmony_ci		ret = dquot->dq_sb->s_op->quota_write(dquot->dq_sb, type,
9962306a36Sopenharmony_ci			(char *)&dqblk, sizeof(struct v1_disk_dqblk),
10062306a36Sopenharmony_ci			v1_dqoff(from_kqid(&init_user_ns, dquot->dq_id)));
10162306a36Sopenharmony_ci	if (ret != sizeof(struct v1_disk_dqblk)) {
10262306a36Sopenharmony_ci		quota_error(dquot->dq_sb, "dquota write failed");
10362306a36Sopenharmony_ci		if (ret >= 0)
10462306a36Sopenharmony_ci			ret = -EIO;
10562306a36Sopenharmony_ci		goto out;
10662306a36Sopenharmony_ci	}
10762306a36Sopenharmony_ci	ret = 0;
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ciout:
11062306a36Sopenharmony_ci	dqstats_inc(DQST_WRITES);
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	return ret;
11362306a36Sopenharmony_ci}
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci/* Magics of new quota format */
11662306a36Sopenharmony_ci#define V2_INITQMAGICS {\
11762306a36Sopenharmony_ci	0xd9c01f11,     /* USRQUOTA */\
11862306a36Sopenharmony_ci	0xd9c01927      /* GRPQUOTA */\
11962306a36Sopenharmony_ci}
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci/* Header of new quota format */
12262306a36Sopenharmony_cistruct v2_disk_dqheader {
12362306a36Sopenharmony_ci	__le32 dqh_magic;        /* Magic number identifying file */
12462306a36Sopenharmony_ci	__le32 dqh_version;      /* File version */
12562306a36Sopenharmony_ci};
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_cistatic int v1_check_quota_file(struct super_block *sb, int type)
12862306a36Sopenharmony_ci{
12962306a36Sopenharmony_ci	struct inode *inode = sb_dqopt(sb)->files[type];
13062306a36Sopenharmony_ci	ulong blocks;
13162306a36Sopenharmony_ci	size_t off;
13262306a36Sopenharmony_ci	struct v2_disk_dqheader dqhead;
13362306a36Sopenharmony_ci	ssize_t size;
13462306a36Sopenharmony_ci	loff_t isize;
13562306a36Sopenharmony_ci	static const uint quota_magics[] = V2_INITQMAGICS;
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	isize = i_size_read(inode);
13862306a36Sopenharmony_ci	if (!isize)
13962306a36Sopenharmony_ci		return 0;
14062306a36Sopenharmony_ci	blocks = isize >> BLOCK_SIZE_BITS;
14162306a36Sopenharmony_ci	off = isize & (BLOCK_SIZE - 1);
14262306a36Sopenharmony_ci	if ((blocks % sizeof(struct v1_disk_dqblk) * BLOCK_SIZE + off) %
14362306a36Sopenharmony_ci	    sizeof(struct v1_disk_dqblk))
14462306a36Sopenharmony_ci		return 0;
14562306a36Sopenharmony_ci	/* Doublecheck whether we didn't get file with new format - with old
14662306a36Sopenharmony_ci	 * quotactl() this could happen */
14762306a36Sopenharmony_ci	size = sb->s_op->quota_read(sb, type, (char *)&dqhead,
14862306a36Sopenharmony_ci				    sizeof(struct v2_disk_dqheader), 0);
14962306a36Sopenharmony_ci	if (size != sizeof(struct v2_disk_dqheader))
15062306a36Sopenharmony_ci		return 1;	/* Probably not new format */
15162306a36Sopenharmony_ci	if (le32_to_cpu(dqhead.dqh_magic) != quota_magics[type])
15262306a36Sopenharmony_ci		return 1;	/* Definitely not new format */
15362306a36Sopenharmony_ci	printk(KERN_INFO
15462306a36Sopenharmony_ci	       "VFS: %s: Refusing to turn on old quota format on given file."
15562306a36Sopenharmony_ci	       " It probably contains newer quota format.\n", sb->s_id);
15662306a36Sopenharmony_ci        return 0;		/* Seems like a new format file -> refuse it */
15762306a36Sopenharmony_ci}
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_cistatic int v1_read_file_info(struct super_block *sb, int type)
16062306a36Sopenharmony_ci{
16162306a36Sopenharmony_ci	struct quota_info *dqopt = sb_dqopt(sb);
16262306a36Sopenharmony_ci	struct v1_disk_dqblk dqblk;
16362306a36Sopenharmony_ci	int ret;
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	down_read(&dqopt->dqio_sem);
16662306a36Sopenharmony_ci	ret = sb->s_op->quota_read(sb, type, (char *)&dqblk,
16762306a36Sopenharmony_ci				sizeof(struct v1_disk_dqblk), v1_dqoff(0));
16862306a36Sopenharmony_ci	if (ret != sizeof(struct v1_disk_dqblk)) {
16962306a36Sopenharmony_ci		if (ret >= 0)
17062306a36Sopenharmony_ci			ret = -EIO;
17162306a36Sopenharmony_ci		goto out;
17262306a36Sopenharmony_ci	}
17362306a36Sopenharmony_ci	ret = 0;
17462306a36Sopenharmony_ci	/* limits are stored as unsigned 32-bit data */
17562306a36Sopenharmony_ci	dqopt->info[type].dqi_max_spc_limit = 0xffffffffULL << QUOTABLOCK_BITS;
17662306a36Sopenharmony_ci	dqopt->info[type].dqi_max_ino_limit = 0xffffffff;
17762306a36Sopenharmony_ci	dqopt->info[type].dqi_igrace =
17862306a36Sopenharmony_ci			dqblk.dqb_itime ? dqblk.dqb_itime : MAX_IQ_TIME;
17962306a36Sopenharmony_ci	dqopt->info[type].dqi_bgrace =
18062306a36Sopenharmony_ci			dqblk.dqb_btime ? dqblk.dqb_btime : MAX_DQ_TIME;
18162306a36Sopenharmony_ciout:
18262306a36Sopenharmony_ci	up_read(&dqopt->dqio_sem);
18362306a36Sopenharmony_ci	return ret;
18462306a36Sopenharmony_ci}
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_cistatic int v1_write_file_info(struct super_block *sb, int type)
18762306a36Sopenharmony_ci{
18862306a36Sopenharmony_ci	struct quota_info *dqopt = sb_dqopt(sb);
18962306a36Sopenharmony_ci	struct v1_disk_dqblk dqblk;
19062306a36Sopenharmony_ci	int ret;
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci	down_write(&dqopt->dqio_sem);
19362306a36Sopenharmony_ci	ret = sb->s_op->quota_read(sb, type, (char *)&dqblk,
19462306a36Sopenharmony_ci				sizeof(struct v1_disk_dqblk), v1_dqoff(0));
19562306a36Sopenharmony_ci	if (ret != sizeof(struct v1_disk_dqblk)) {
19662306a36Sopenharmony_ci		if (ret >= 0)
19762306a36Sopenharmony_ci			ret = -EIO;
19862306a36Sopenharmony_ci		goto out;
19962306a36Sopenharmony_ci	}
20062306a36Sopenharmony_ci	spin_lock(&dq_data_lock);
20162306a36Sopenharmony_ci	dqopt->info[type].dqi_flags &= ~DQF_INFO_DIRTY;
20262306a36Sopenharmony_ci	dqblk.dqb_itime = dqopt->info[type].dqi_igrace;
20362306a36Sopenharmony_ci	dqblk.dqb_btime = dqopt->info[type].dqi_bgrace;
20462306a36Sopenharmony_ci	spin_unlock(&dq_data_lock);
20562306a36Sopenharmony_ci	ret = sb->s_op->quota_write(sb, type, (char *)&dqblk,
20662306a36Sopenharmony_ci	      sizeof(struct v1_disk_dqblk), v1_dqoff(0));
20762306a36Sopenharmony_ci	if (ret == sizeof(struct v1_disk_dqblk))
20862306a36Sopenharmony_ci		ret = 0;
20962306a36Sopenharmony_ci	else if (ret >= 0)
21062306a36Sopenharmony_ci		ret = -EIO;
21162306a36Sopenharmony_ciout:
21262306a36Sopenharmony_ci	up_write(&dqopt->dqio_sem);
21362306a36Sopenharmony_ci	return ret;
21462306a36Sopenharmony_ci}
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_cistatic const struct quota_format_ops v1_format_ops = {
21762306a36Sopenharmony_ci	.check_quota_file	= v1_check_quota_file,
21862306a36Sopenharmony_ci	.read_file_info		= v1_read_file_info,
21962306a36Sopenharmony_ci	.write_file_info	= v1_write_file_info,
22062306a36Sopenharmony_ci	.read_dqblk		= v1_read_dqblk,
22162306a36Sopenharmony_ci	.commit_dqblk		= v1_commit_dqblk,
22262306a36Sopenharmony_ci};
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_cistatic struct quota_format_type v1_quota_format = {
22562306a36Sopenharmony_ci	.qf_fmt_id	= QFMT_VFS_OLD,
22662306a36Sopenharmony_ci	.qf_ops		= &v1_format_ops,
22762306a36Sopenharmony_ci	.qf_owner	= THIS_MODULE
22862306a36Sopenharmony_ci};
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_cistatic int __init init_v1_quota_format(void)
23162306a36Sopenharmony_ci{
23262306a36Sopenharmony_ci        return register_quota_format(&v1_quota_format);
23362306a36Sopenharmony_ci}
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_cistatic void __exit exit_v1_quota_format(void)
23662306a36Sopenharmony_ci{
23762306a36Sopenharmony_ci        unregister_quota_format(&v1_quota_format);
23862306a36Sopenharmony_ci}
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_cimodule_init(init_v1_quota_format);
24162306a36Sopenharmony_cimodule_exit(exit_v1_quota_format);
24262306a36Sopenharmony_ci
243